The problem with manual Swagger decorators
Every NestJS project using OpenAPI documentation eventually hits the same wall: your DTO files become bloated with @ApiProperty decorators that duplicate information already present in your TypeScript types and validation rules. If you're managing a microservice with fifty endpoints and dozens of DTOs, you end up writing metadata twice—once in your class definition and again in your Swagger annotations. The Swagger CLI plugin: a practical guide for developers exists specifically to solve this repetition problem.
By the time you've decorated half a dozen request and response objects, the maintenance burden becomes real. Every time a field changes type or validation rules are updated, you must remember to update both the class definition and the decorator metadata. Teams often discover this disconnect weeks later when the API documentation no longer matches the actual validation behavior.
Enable the plugin in nest-cli.json
Getting started requires only a minimal configuration change. Open your nest-cli.json file and add the Swagger plugin to your compilerOptions:
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger/plugin",
"options": {
"classValidatorShim": true,
"introspectComments": true,
"dtoFileNameSuffix": [".dto.ts", ".entity.ts"]
}
}
]
}
}
The three core options deserve attention. classValidatorShim enables the plugin to read your class-validator decorators like @IsString, @IsEmail, and @Min—these become part of your generated Swagger schema without any additional work. introspectComments parses JSDoc comments above your fields and pulls them into the API documentation automatically. dtoFileNameSuffix tells the plugin which files to process; adjust this array if your project uses different naming conventions.
Once you save this configuration, the plugin activates on every compilation. Run npm run build or start your dev server and the transformation happens silently during the TypeScript compilation phase.
How the plugin reads TypeScript types and class-validator rules
The plugin acts as an intelligent metadata extractor. When you compile your project, it examines each class that matches your DTO suffix patterns and builds a complete understanding of each field's type, constraints, and documentation.
Consider a real-world scenario: a user registration DTO in a multi-tenant SaaS application might look like this:
import { IsEmail, IsString, MinLength, MaxLength, Matches } from 'class-validator';
export class CreateUserDto {
/**
* User's email address
* Must be unique across the system
*/
@IsEmail()
email: string;
/**
* User's full name
*/
@IsString()
@MinLength(2)
@MaxLength(100)
name: string;
/**
* Password (minimum 8 characters, must include uppercase, lowercase, and number)
*/
@IsString()
@MinLength(8)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
password: string;
/**
* Department identifier
*/
@IsUUID('4')
departmentId: string;
}
Without the CLI plugin, you'd manually add @ApiProperty decorators to every field, repeating the type information TypeScript already knew and duplicating validation metadata from class-validator. The plugin eliminates this duplication by reading both the TypeScript type annotation (string, number, etc.) and all the class-validator decorators at compile time.
The extraction process is context-aware. The plugin understands that @IsEmail means the field must be a valid email address, that @MinLength(8) sets a minimum constraint, and that @Matches describes a regex pattern. It builds a complete picture of each field's requirements from these multiple sources.
Generates property metadata at compile time
What makes this approach powerful is the timing: metadata generation happens during compilation, not at runtime. This means zero runtime overhead. The plugin transforms your source code before it reaches the JavaScript execution layer, so your production bundle doesn't carry the burden of reflection or additional type-checking logic.
Behind the scenes, the plugin generates the necessary @ApiProperty metadata in memory and applies it to your classes as part of the compilation process. If you were to inspect the compiled JavaScript output, you'd see that the decorators had been applied, but you never had to write them manually.
For a trading platform processing thousands of market data updates, this distinction matters. Your DTOs describing trade execution responses, order confirmations, and market snapshots all benefit from automatically generated metadata without slowing down validation or serialization in hot code paths.
The plugin also respects inheritance and composition patterns. If you have a base PaginatedDto that your response objects extend, the plugin correctly inherits and combines metadata from parent classes with child-specific properties.
Halves the decorators in your DTOs
The practical impact on your codebase is immediate and visible. Compare two approaches to documenting a payment processing DTO:
Without the plugin (verbose):
import { IsString, IsNumber, IsEnum, Min, Max } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class ProcessPaymentDto {
@ApiProperty({
description: 'Payment amount in cents',
type: Number,
minimum: 1,
example: 5000,
})
@IsNumber()
@Min(1)
amount: number;
@ApiProperty({
description: 'Three-letter currency code',
type: String,
example: 'USD',
})
@IsString()
currency: string;
@ApiProperty({
enum: ['credit_card', 'bank_transfer', 'wallet'],
description: 'Payment method',
})
@IsEnum(['credit_card', 'bank_transfer', 'wallet'])
method: 'credit_card' | 'bank_transfer' | 'wallet';
}
With the plugin (clean):
import { IsString, IsNumber, IsEnum, Min } from 'class-validator';
export class ProcessPaymentDto {
/**
* Payment amount in cents (minimum 1 cent)
* @example 5000
*/
@IsNumber()
@Min(1)
amount: number;
/**
* Three-letter currency code
* @example USD
*/
@IsString()
currency: string;
/**
* Payment method
*/
@IsEnum(['credit_card', 'bank_transfer', 'wallet'])
method: 'credit_card' | 'bank_transfer' | 'wallet';
}
The second version is cleaner and more maintainable. Your JSDoc comments replace Swagger description fields, class-validator decorators provide the constraints and type information, and TypeScript's type system ensures correctness. The plugin reads all of this during compilation and synthesizes complete Swagger documentation without you writing a single @ApiProperty.
For large codebases, this reduces decorator volume by 50-70% depending on how thoroughly your DTOs are already decorated with class-validator rules. A ride-hailing service with DTOs for driver locations, passenger requests, ride history, and payment details sees significant file size reductions and improved readability across the board.
Common mistakes
Forgetting to name your DTOs consistently: The plugin looks for files matching your dtoFileNameSuffix patterns. If you name files user.dto.ts but configure the suffix as [".dto.ts"], it works. But if you have orphaned user-types.ts files without the suffix, the plugin ignores them. Be explicit about your naming conventions across the team.
Missing class-validator decorators: The plugin synthesizes metadata from your validation decorators. If you have a field with no validation rules and no JSDoc comment, the generated Swagger metadata will be minimal (though the type will still be captured). Add at least basic validators like @IsString() or descriptive comments for all public API fields.
Not updating nest-cli.json in all environments: If your build pipeline, CI/CD system, or team member's local setup uses an outdated or incorrect Mixing manual and automatic decorators: If you explicitly add nest-cli.json
@ApiProperty decorators to fields the plugin also processes, you may see unexpected behavior or duplicate metadata. Either let the plugin handle it entirely or manage metadata manually across your entire codebase—mixing approaches creates confusion during maintenance.Key takeaways
nest-cli.json under compilerOptions.plugins with classValidatorShim: true and introspectComments: true to extract type and validation metadata automatically.@ApiProperty decorators.






Comments
Loading comments…