How it works

No monkey-patching, no runtime proxies: just the right timing and Reflect metadata.

The pipeline

DocfyModule.forRoot() runs synchronously during the NestFactory.create() phase, before SwaggerModule.createDocument() is called. It uses require() to load each companion docs file, which executes docs() and writes Reflect metadata directly onto the controller's methods, exactly as TypeScript's decorator syntax would at class-definition time.

By the time SwaggerModule.createDocument() scans for metadata, everything is already in place. No monkey-patching, no runtime proxies.

Timing

Key timing insight

onModuleInit and other lifecycle hooks fire after SwaggerModule.createDocument() has already been called in main.ts. Any approach based on onModuleInit would produce empty Swagger documentation. That's why nestjs-docfy avoids that path and uses a synchronous require() inside forRoot(), which runs during NestFactory.create(), before any lifecycle hook or SwaggerModule call.

CLI: static analysis

The generate CLI uses ts-morph for static analysis: it reads the TypeScript AST without executing any project code. All user-supplied paths and glob patterns are validated and sanitized before use.