Swagger Setup
A REST API is only as useful as its documentation, and hand-written docs drift out of sync the moment the code changes. NestJS solves this with the official @nestjs/swagger package, which inspects your controllers, DTOs, and decorators at startup to produce a live OpenAPI specification. From that spec it serves a fully interactive Swagger UI where consumers can read every route and fire real requests. This page covers installing the package, configuring the spec with DocumentBuilder, building the document, and mounting Swagger UI with SwaggerModule.setup.
Installing the package
@nestjs/swagger ships separately from the core framework. Install it from npm; it pulls in swagger-ui-express automatically when you run an Express-based application (the default).
npm install @nestjs/swagger
If your application runs on Fastify instead of Express, install the matching UI adapter so the static assets resolve correctly.
npm install @nestjs/swagger @fastify/static
Generate Swagger documents only outside of production, or gate the route behind an environment check. Exposing your full API surface and a request console publicly can leak internal endpoints.
Configuring the spec with DocumentBuilder
The DocumentBuilder is a fluent builder that assembles the top-level metadata of your OpenAPI document — the title, description, version, and global features such as security schemes or tags. You construct it inside bootstrap, after the application instance exists, because the document must reflect every module that has been loaded.
Each method returns the builder, so calls chain naturally, and build() produces a plain configuration object that the SwaggerModule consumes.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Cats API')
.setDescription('Manage the cattery: breeds, owners, and adoptions')
.setVersion('1.0')
.addTag('cats', 'Operations on individual cats')
.addBearerAuth()
.build();
// ...document + setup below
await app.listen(3000);
}
bootstrap();
The most commonly used builder methods are summarized below.
| Method | Purpose |
|---|---|
setTitle(title) | The API name shown at the top of the UI |
setDescription(text) | A longer summary rendered under the title (Markdown supported) |
setVersion(version) | The API version string, e.g. '1.0' |
addTag(name, description?) | Declares a tag used to group related endpoints |
addBearerAuth() | Registers a JWT bearer security scheme and an Authorize button |
addServer(url) | Lists a base URL (handy for staging vs. production) |
Creating the document
With the configuration ready, call SwaggerModule.createDocument(app, config). This is where the package scans the application: it walks the controllers registered in your modules, reads route decorators like @Get and @Post, and reflects DTO classes to build request and response schemas. The result is a complete OpenAPIObject held in memory.
const document = SwaggerModule.createDocument(app, config);
You can pass a third options argument to refine the scan. operationIdFactory controls how operation IDs are generated (useful for client codegen), and deepScanRoutes ensures routes nested through dynamic modules are included.
const document = SwaggerModule.createDocument(app, config, {
operationIdFactory: (controllerKey, methodKey) => methodKey,
deepScanRoutes: true,
});
Serving Swagger UI with SwaggerModule.setup
Finally, mount the interactive UI with SwaggerModule.setup(path, app, document). The first argument is the route prefix; visiting it in a browser renders Swagger UI, while the raw JSON spec is served automatically at the same path with a -json suffix (e.g. /docs-json).
// src/main.ts (complete)
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Cats API')
.setDescription('Manage the cattery: breeds, owners, and adoptions')
.setVersion('1.0')
.addTag('cats', 'Operations on individual cats')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document, {
jsonDocumentUrl: 'docs/json',
swaggerOptions: {
persistAuthorization: true,
tagsSorter: 'alpha',
operationsSorter: 'alpha',
},
});
await app.listen(3000);
console.log('Swagger UI: http://localhost:3000/docs');
}
bootstrap();
Output:
Swagger UI: http://localhost:3000/docs
The fourth argument, SwaggerCustomOptions, tunes the experience. persistAuthorization keeps your entered bearer token across page reloads, jsonDocumentUrl overrides the default -json route, and the swaggerOptions object passes settings straight through to the underlying Swagger UI bundle.
| Option | Effect |
|---|---|
jsonDocumentUrl | Custom path for the raw OpenAPI JSON |
swaggerOptions.persistAuthorization | Retains the Authorize token after a refresh |
swaggerOptions.tagsSorter | 'alpha' sorts tag groups alphabetically |
customSiteTitle | Overrides the browser tab title |
useGlobalPrefix | Prefixes the docs route with the app’s global prefix |
Exporting the spec to a file
Because createDocument returns a plain object, you can serialize it to disk for committing alongside the repo or feeding to a client generator. This is a common step in CI pipelines.
import { writeFileSync } from 'node:fs';
const document = SwaggerModule.createDocument(app, config);
writeFileSync('./openapi.json', JSON.stringify(document, null, 2));
Best Practices
- Build the document inside
bootstrapafterNestFactory.createso the scan sees every loaded module and route. - Set a meaningful title, description, and version with
DocumentBuilder— these become the front page of your API for every consumer. - Gate the Swagger route behind a non-production environment check, or protect it, to avoid exposing your full API surface publicly.
- Enable
persistAuthorizationso testers do not re-enter their bearer token on every reload. - Declare security schemes once with
addBearerAuthrather than annotating each route by hand. - Export
openapi.jsonin CI to drive contract tests and typed client generation, keeping consumers in lockstep with the server.