Nestjs — Stop burning AI credits to write Swagger docs, let the CLI do it!

typescript dev.to

Last Sunday I shared nestjs-docfy, a small library to move Swagger decorators out of NestJS controllers into companion *.controller.docs.ts files. The reception was better than I expected, and a lot of the feedback pointed in the same direction: the separation is nice, but writing those docs files by hand is still tedious.

So I spent some time on that, and there's quite a bit new in this release.

A CLI that writes the boilerplate for you

The biggest addition is a generate command that reads your project with static analysis (no code execution, no ts-node overhead) and produces a pre-filled docs file for every controller:

npx nestjs-docfy generate
Enter fullscreen mode Exit fullscreen mode

The generated file comes with inferred summaries, response types, and common error responses already in place. You edit from there instead of starting from scratch.

It's idempotent by default, running it again won't touch files that already exist. When you add a new endpoint and want to merge only the new method block without losing your existing edits:

npx nestjs-docfy generate --force
Enter fullscreen mode Exit fullscreen mode

The CLI auto-detects your project layout, so monorepos (Nx, Nest CLI, generic packages/ or apps/ structures) work without any configuration.

There's also a --dry-run flag if you want to preview output before writing anything to disk.

A check command for CI

The other side of the workflow is keeping docs in sync as the codebase evolves. The check command exits with code 1 if any controller has undocumented methods or no companion file at all:

npx nestjs-docfy check
Enter fullscreen mode Exit fullscreen mode

Output looks like this when something is out of sync:

✖ UsersController, undocumented methods: updateProfile, deleteAccount
  → run nestjs-docfy generate --force to merge new methods

✖ 2 controller(s) out of sync.
Enter fullscreen mode Exit fullscreen mode

Drop it into your pipeline and docs drift gets caught before it reaches main.

Type-safe method keys

The docs() function now enforces that every key in config.methods actually exists on the controller class. Typos are a compile error, not a silent runtime warning:

docs(UsersController, {
  methods: {
    findAll: [...],    // ✔ exists on UsersController
    typoMethod: [...], // ✖ TypeScript error
  },
});
Enter fullscreen mode Exit fullscreen mode

Interface-typed DTOs just work

This one came up a lot. If your response or body type is a TypeScript interface, Swagger can't use it as a type: value because interfaces are erased at runtime. Previously you had to convert them to classes or write the schema by hand.

Now the CLI detects this automatically and generates an inline schema: object instead:

// Your existing interface — no changes needed
export interface RegisterResponseDto {
  success: boolean;
  message: string | null;
}
Enter fullscreen mode Exit fullscreen mode

Generated output:

ApiResponse({
  status: 201,
  description: 'Created',
  schema: {
    type: 'object',
    properties: {
      success: { type: 'boolean' },
      message: { type: 'string', nullable: true },
    },
    required: ['success'],
  },
}),
Enter fullscreen mode Exit fullscreen mode

Supports primitives, nullable unions, arrays, nested interfaces, and optional properties.

class-validator inference

If a DTO uses class-validator decorators and doesn't already have @ApiProperty on its properties, the CLI infers the full JSON Schema from the validators — no manual annotation needed:

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsEmail()
  email: string;

  @IsOptional()
  @IsString()
  bio?: string;
}
Enter fullscreen mode Exit fullscreen mode

Generated output:

ApiBody({
  schema: {
    type: 'object',
    properties: {
      name:  { type: 'string', minLength: 2 },
      email: { type: 'string', format: 'email' },
      bio:   { type: 'string' },
    },
    required: ['name', 'email'],
  },
}),
Enter fullscreen mode Exit fullscreen mode

If any property already has @ApiProperty, inference is skipped entirely and type: ClassName is used instead, existing annotations are never overwritten.

@HttpCode() awareness

The CLI reads @HttpCode() on route handlers and uses the correct status code in the generated ApiResponse. A @Post with @HttpCode(204) gets status: 204, not the default 201.


The core idea is still the same — controllers express behavior, docs files express documentation, but now the friction of setting that up and keeping it current is mostly gone.

🔗 nestjs-docfy on GitHub · 📦 npm package

If you were holding off because of the manual setup, this release should make it a lot more practical.

Source: dev.to

arrow_back Back to Tutorials