What is nestjs-docfy

A NestJS tooling layer that moves Swagger decorators out of controllers, without losing typing or generating a different OpenAPI output.

The problem

NestJS controllers documented with @nestjs/swagger quickly accumulate dozens of @ApiOperation, @ApiResponse, @ApiBody, and @ApiTags, to the point where the route's real logic ends up buried under documentation metadata.

The solution

nestjs-docfy introduces a simple convention: for every *.controller.ts, there's a *.controller.docs.ts alongside it, holding all the documentation. It's the same pattern Nest already uses for *.spec.ts.

  • Zero change to the generated OpenAPI output.
  • Typing preserved: the companion file imports the controller class.
  • Discovery via require.cache, before SwaggerModule.createDocument().

Before and after

users.controller.ts
@WithDocs()
@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.users.findOne(id);
  }
}
users.controller.docs.ts
docs(UsersController, {
  classDecorators: [ApiTags('Users')],
  methods: {
    findOne: [
      ApiParam({ name: 'id', type: String }),
      ApiOperation({ summary: 'Get user by id' }),
      ApiResponse({ status: 200, type: UserDto }),
      ApiResponse({ status: 404, description: 'User not found' }),
    ],
  },
});
Known limitation

Companion file discovery depends on require.cache, so it doesn't work with nest-cli.json configured with "webpack": true. For those projects, use the alternative flow with nestjs-docfy patch-spec.

When to use it

It's a good fit when

  • Your API has dozens of endpoints and several responses per route.
  • Your team treats OpenAPI as a versioned contract.
  • You want objective CI gates (minimum coverage, docs linting).
AI-first

The companion package docfy-ui renders the API reference with a Copy for AI button on every endpoint, ideal for pasting into LLMs.