NestJS Logo

Introduction

The OpenAPI specification is a language-agnostic definition format used to describe RESTful APIs. Nest provides a dedicated module which allows you to generate such a specification by leveraging decorators.

Installation#

To begin using it, we first install the required dependency.


$ npm install --save @nestjs/swagger
Warning When using fastify you also need to install @fastify/static:

$ npm install --save @fastify/static

Bootstrap#

Once the installation process is complete, open the main.ts file and initialize Swagger using the SwaggerModule class:

main.ts
JS TS

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .build();
  const documentFactory = () => SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, documentFactory);

  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();
Hint The factory method SwaggerModule.createDocument() is used specifically to generate the Swagger document when you request it. This approach helps save some initialization time, and the resulting document is a serializable object that conforms to the OpenAPI Document specification. Instead of serving the document over HTTP, you can also save it as a JSON or YAML file and use it in various ways.
Warning If you call createDocument() eagerly (rather than in a factory function, as shown above), make sure app.enableVersioning() runs first — otherwise the generated paths will be missing the version prefix. The factory pattern is unaffected, since the document is not built until it is first requested.

The DocumentBuilder helps to structure a base document that conforms to the OpenAPI Specification. It provides several methods that allow you to set properties such as title, description, and version. In order to create a full document (with all HTTP routes defined) we use the createDocument() method of the SwaggerModule class. This method takes two arguments, an application instance and a Swagger options object. Alternatively, we can provide a third argument, which should be of type SwaggerDocumentOptions. More on this in the Document options section.

Once we create a document, we can call the setup() method. It accepts:

  1. The path to mount the Swagger UI
  2. An application instance
  3. The document object instantiated above
  4. Optional configuration parameter (read more here)

Now you can run the following command to start the HTTP server:


$ npm run start

While the application is running, open your browser and navigate to http://localhost:3000/api. You should see the Swagger UI.

As you can see, the SwaggerModule automatically reflects all of your endpoints.

Hint To generate and download a Swagger JSON file, navigate to http://localhost:3000/api-json (assuming that your Swagger documentation is available under http://localhost:3000/api). It is also possible to expose it on a route of your choice using only the setup method from @nestjs/swagger, like this:

SwaggerModule.setup('swagger', app, documentFactory, {
  jsonDocumentUrl: 'swagger/json',
});

Which would expose it at http://localhost:3000/swagger/json

Warning When using fastify and helmet, there may be a problem with CSP, to solve this collision, configure the CSP as shown below:

app.register(helmet, {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: [`'self'`],
      styleSrc: [`'self'`, `'unsafe-inline'`],
      imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
      scriptSrc: [`'self'`, `https:`, `'unsafe-inline'`],
    },
  },
});

// If you are not going to use CSP at all, you can use this:
app.register(helmet, {
  contentSecurityPolicy: false,
});

Document options#

When creating a document, it is possible to provide some extra options to fine tune the library's behavior. These options should be of type SwaggerDocumentOptions, which can be the following:


export interface SwaggerDocumentOptions {
  /**
   * List of modules to include in the specification
   */
  include?: Function[];

  /**
   * Additional, extra models that should be inspected and included in the specification
   */
  extraModels?: Function[];

  /**
   * If `true`, swagger will ignore the global prefix set through `setGlobalPrefix()` method
   */
  ignoreGlobalPrefix?: boolean;

  /**
   * If `true`, swagger will also load routes from the modules imported by `include` modules
   */
  deepScanRoutes?: boolean;

  /**
   * Custom operationIdFactory that will be used to generate the `operationId`
   * based on the `controllerKey`, `methodKey`, and version.
   * @default () => controllerKey_methodKey_version
   */
  operationIdFactory?: OperationIdFactory;

  /**
   * Custom linkNameFactory that will be used to generate the name of links
   * in the `links` field of responses
   *
   * @see [Link objects](https://swagger.io/docs/specification/links/)
   *
   * @default () => `${controllerKey}_${methodKey}_from_${fieldKey}`
   */
  linkNameFactory?: (
    controllerKey: string,
    methodKey: string,
    fieldKey: string
  ) => string;

  /*
   * Generate tags automatically based on the controller name.
   * If `false`, you must use the `@ApiTags()` decorator to define tags.
   * Otherwise, the controller name without the suffix `Controller` will be used.
   * @default true
   */
  autoTagControllers?: boolean;
}

For example, if you want to make sure that the library generates operation names like createUser instead of UsersController_createUser, you can set the following:


const options: SwaggerDocumentOptions =  {
  operationIdFactory: (
    controllerKey: string,
    methodKey: string
  ) => methodKey
};
const documentFactory = () => SwaggerModule.createDocument(app, config, options);

Standard Schema (Zod, Valibot)#

Nest route parameter decorators accept a Standard Schema compatible schema through their schema option (see the Controllers chapter):

cats.controller.ts
JS TS

import { z } from 'zod';

const createCatSchema = z.object({
  name: z.string(),
  age: z.number().int().positive(),
  breed: z.string(),
});

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body({ schema: createCatSchema }) createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }
}

The Swagger module picks these schemas up and turns them into request bodies and parameters in the generated document.

Libraries that need no configuration

If your validation library implements the Standard JSON Schema extension - meaning its schemas expose ~standard.jsonSchema - Nest converts them on its own, and there is nothing to configure. It requests the openapi-3.0 target and uses the input or output variant depending on whether the schema describes a request or a response.

Supplying a converter

For libraries that do not expose that extension, provide a standardSchemaConverter in SwaggerDocumentOptions. It receives the raw schema plus the schemaType being generated, and returns the converted OpenAPI schema:


standardSchemaConverter?: (
  schema: unknown,
  options: { schemaType: 'input' | 'output' },
) => { schema: unknown; components?: Record<string, any> } | undefined;

Returning undefined tells Nest that the converter does not handle this schema, so it falls back to the native conversion described above. That is what makes it safe to support several libraries from one converter.

For Zod, use zod-openapi:


$ npm i --save-dev zod-openapi
main.ts
JS TS

import { SwaggerDocumentOptions } from '@nestjs/swagger';
import { createSchema } from 'zod-openapi';

const documentOptions: SwaggerDocumentOptions = {
  standardSchemaConverter: (schema, { schemaType }) => {
    const converted = createSchema(schema as never, {
      io: schemaType,
      openapiVersion: '3.0.0',
    });
    return { schema: converted.schema, components: converted.components };
  },
};

const documentFactory = () =>
  SwaggerModule.createDocument(app, config, documentOptions);

For Valibot, use @valibot/to-json-schema:


$ npm i --save-dev @valibot/to-json-schema
main.ts
JS TS

import { toJsonSchema } from '@valibot/to-json-schema';

const documentOptions: SwaggerDocumentOptions = {
  standardSchemaConverter: (schema, { schemaType }) => ({
    schema: toJsonSchema(schema as never, {
      target: 'openapi-3.0',
      typeMode: schemaType,
    }),
  }),
};

Note the difference between the two: createSchema() can hoist reusable definitions, so its result carries a components map that you pass through and Nest merges into the document's shared components. toJsonSchema() returns a single self-contained schema, so components is simply omitted.

Supporting several libraries at once

Because the converter receives the schema as unknown, you can branch on the schema's vendor and support more than one library in the same application. Every Standard Schema exposes it at ~standard.vendor:

main.ts
JS TS

import { toJsonSchema } from '@valibot/to-json-schema';
import { createSchema } from 'zod-openapi';

function hasVendor(schema: unknown, vendor: string) {
  return (
    !!schema &&
    typeof schema === 'object' &&
    (schema as { '~standard'?: { vendor?: string } })['~standard']?.vendor ===
      vendor
  );
}

const documentOptions: SwaggerDocumentOptions = {
  standardSchemaConverter: (schema, { schemaType }) => {
    if (hasVendor(schema, 'zod')) {
      const converted = createSchema(schema as never, {
        io: schemaType,
        openapiVersion: '3.0.0',
      });
      return { schema: converted.schema, components: converted.components };
    }

    if (hasVendor(schema, 'valibot')) {
      return {
        schema: toJsonSchema(schema as never, {
          target: 'openapi-3.0',
          typeMode: schemaType,
        }),
      };
    }

    // Not handled here - let Nest fall back to native conversion
    return undefined;
  },
};
Warning Always narrow by vendor before calling a library-specific converter. Passing a Valibot schema to createSchema() (or the reverse) throws at document generation time rather than failing gracefully.
HintschemaType matters when your schema performs transformations: the input shape is what clients send, and the output shape is what your handler receives after parsing. Nest asks for whichever is appropriate for the position being documented, so pass the value straight through to your converter rather than hardcoding one.

Setup options#

You can configure Swagger UI by passing the options object which fulfills the SwaggerCustomOptions interface as a fourth argument of the SwaggerModule#setup method.


export interface SwaggerCustomOptions {
  /**
   * If `true`, Swagger resources paths will be prefixed by the global prefix set through `setGlobalPrefix()`.
   * Default: `false`.
   * @see https://docs.nestjs.com/faq/global-prefix
   */
  useGlobalPrefix?: boolean;

  /**
   * If `false`, the Swagger UI will not be served. Only API definitions (JSON and YAML)
   * will be accessible (on `/{path}-json` and `/{path}-yaml`). To fully disable both the Swagger UI and API definitions, use `raw: false`.
   * Default: `true`.
   * @deprecated Use `ui` instead.
   */
  swaggerUiEnabled?: boolean;

  /**
   * If `false`, the Swagger UI will not be served. Only API definitions (JSON and YAML)
   * will be accessible (on `/{path}-json` and `/{path}-yaml`). To fully disable both the Swagger UI and API definitions, use `raw: false`.
   * Default: `true`.
   */
  ui?: boolean;

  /**
   * If `true`, raw definitions for all formats will be served.
   * Alternatively, you can pass an array to specify the formats to be served, e.g., `raw: ['json']` to serve only JSON definitions.
   * If omitted or set to an empty array, no definitions (JSON or YAML) will be served.
   * Use this option to control the availability of Swagger-related endpoints.
   * Default: `true`.
   */
  raw?: boolean | Array<'json' | 'yaml'>;

  /**
   * Url point the API definition to load in Swagger UI.
   */
  swaggerUrl?: string;

  /**
   * Path of the JSON API definition to serve.
   * Default: `<path>-json`.
   */
  jsonDocumentUrl?: string;

  /**
   * Path of the YAML API definition to serve.
   * Default: `<path>-yaml`.
   */
  yamlDocumentUrl?: string;

  /**
   * Hook allowing to alter the OpenAPI document before being served.
   * It's called after the document is generated and before it is served as JSON & YAML.
   */
  patchDocumentOnRequest?: <TRequest = any, TResponse = any>(
    req: TRequest,
    res: TResponse,
    document: OpenAPIObject
  ) => OpenAPIObject;

  /**
   * If `true`, the selector of OpenAPI definitions is displayed in the Swagger UI interface.
   * Default: `false`.
   */
  explorer?: boolean;

  /**
   * Additional Swagger UI options
   */
  swaggerOptions?: SwaggerUiOptions;

  /**
   * Custom CSS styles to inject in Swagger UI page.
   */
  customCss?: string;

  /**
   * URL(s) of a custom CSS stylesheet to load in Swagger UI page.
   */
  customCssUrl?: string | string[];

  /**
   * URL(s) of custom JavaScript files to load in Swagger UI page.
   */
  customJs?: string | string[];

  /**
   * Custom JavaScript scripts to load in Swagger UI page.
   */
  customJsStr?: string | string[];

  /**
   * Custom favicon for Swagger UI page.
   */
  customfavIcon?: string;

  /**
   * Custom title for Swagger UI page.
   */
  customSiteTitle?: string;

  /**
   * File system path (ex: ./node_modules/swagger-ui-dist) containing static Swagger UI assets.
   */
  customSwaggerUiPath?: string;

  /**
   * @deprecated This property has no effect.
   */
  validatorUrl?: string;

  /**
   * @deprecated This property has no effect.
   */
  url?: string;

  /**
   * @deprecated This property has no effect.
   */
  urls?: Record<'url' | 'name', string>[];
}
Hintui and raw are independent options. Disabling Swagger UI (ui: false) does not disable API definitions (JSON/YAML). Conversely, disabling API definitions (raw: []) does not disable the Swagger UI.

For example, the following configuration will disable the Swagger UI but still allow access to API definitions:


const options: SwaggerCustomOptions = {
  ui: false, // Swagger UI is disabled
  raw: ['json'], // JSON API definition is still accessible (YAML is disabled)
};
SwaggerModule.setup('api', app, options);

In this case, http://localhost:3000/api-json will still be accessible, but http://localhost:3000/api (Swagger UI) will not.

Example#

A working example is available here.

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor