Skip to content
NestJS ns openapi 4 min read

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.

MethodPurpose
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.

OptionEffect
jsonDocumentUrlCustom path for the raw OpenAPI JSON
swaggerOptions.persistAuthorizationRetains the Authorize token after a refresh
swaggerOptions.tagsSorter'alpha' sorts tag groups alphabetically
customSiteTitleOverrides the browser tab title
useGlobalPrefixPrefixes 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 bootstrap after NestFactory.create so 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 persistAuthorization so testers do not re-enter their bearer token on every reload.
  • Declare security schemes once with addBearerAuth rather than annotating each route by hand.
  • Export openapi.json in CI to drive contract tests and typed client generation, keeping consumers in lockstep with the server.
Last updated June 14, 2026
Was this helpful?