OpenAPI for your REST APIs in NestJS

Setup Swagger to generate an OpenAPI documentation for your REST endpoints.

Authors
Marc Stammerjohann Marc Stammerjohann
Published at

The OpenAPI documentation is a useful API playground for you to test or to share with other developers and for client generation tools (e.g ng-openapi-gen for Angular).

You'll find the source code in this repo.

Setup Swagger

Start with installing the Swagger dependencies.

# nest v9
npm install --save @nestjs/swagger class-transformer class-validator

# nest v8
# express
npm install --save @nestjs/swagger@5 swagger-ui-express class-transformer class-validator

# fastify
npm install --save @nestjs/swagger@5 fastify-swagger class-transformer class-validator

Now setup the initialization of Swagger in your main.ts file.

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('NestJS Swagger')
    .setDescription('API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

The setup is complete, start your Nest application npm run start:dev and visit the Swagger endpoint localhost:3000/api.

Swagger API after initial setup

Swagger API will be available at the path you provide in SwaggerModule.setup('api',...) at http://localhost:3000/api. Access the JSON file by opening http://localhost:3000/api-json for express and http://localhost:3000/api/json for fastify.

Generate in the next step CRUD endpoints for a resource like users or products and add type definitions for Swagger.

Generate REST resource

Use the Nest CLI to generate the boilerplate the resource for users.

nest generate resource
# short
nest g res

# CLI prompts
? What name would you like to use for this resource (plural, e.g., "users")? users
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? (Y/n) y

You'll find a new users directory under src containing all the boilerplates for your REST endpoints - module, controller, service, entity and dto files.

Start again the Nest application and you should see the new users endpoints in the Swagger API.

Users endpoints in the Swagger API

API decorators

Apply available decorators prefixed with API to expose the properties for .dto.ts and .entity.ts files and the responses for your CRUD endpoints.

Tags

Group your endpoints together by using @ApiTags(...tags) at the controller level.

import { Controller } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';

@Controller('users')
@ApiTags('users') // 👈 apply tags
export class UsersController {
  ...
}

Group endpoints with tags

Property

Let's add the following properties name, email, password to the CreateUserDto and mark name as optional.

export class CreateUserDto {
  email: string;
  password: string;
  name?: string | null;
}

To expose those properties to the Swagger API use @ApiProperty(options) at the property level and pass options like required, default, description and more.

import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
  @ApiProperty()
  email: string;
  @ApiProperty()
  password: string;
  @ApiProperty({ required: false, nullable: true })
  name?: string | null;
}

Refresh the Swagger API and you should see the properties for the CreateUserDto.

CreateUserDto properties with name optional

Also have a look at the UpdateUserDto schema in Swagger. The same properties are shown but all of them are marked as optional. This is because of PartialType also called Mapped types provided by Nest.

import { PartialType } from '@nestjs/swagger';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

PartialType applies the same properties from CreateUserDto but set to optional.

Response

Add the same properties as before to the user.entity.ts and only expose name and email to Swagger.

import { ApiProperty } from '@nestjs/swagger';

export class User {
  @ApiProperty()
  email: string;
  password: string;
  @ApiProperty({ required: false, nullable: true })
  name?: string | null;
}

Additionally, Swagger needs help to pick up the response type. Annotate your REST endpoints with the custom @ApiResponse() specifying the status code and the response type or choose a short-hand API response (e.g. @ApiOkResponse(), @ApiCreatedResponse(), ...).

  • @ApiOkResponse: GET and DELETE
  • @ApiCreatedResponse: POST and PATCH
  • @ApiForbiddenResponse: endpoint might throw forbidden (403) exception
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';

@Controller('users')
@ApiTags('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  @ApiCreatedResponse({ type: User })
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Get()
  @ApiOkResponse({ type: [User] }) // 👈 array notation
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  @ApiOkResponse({ type: User })
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id);
  }

  @Patch(':id')
  @ApiCreatedResponse({ type: User })
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(+id, updateUserDto);
  }

  @Delete(':id')
  @ApiOkResponse({ type: User })
  remove(@Param('id') id: string) {
    return this.usersService.remove(+id);
  }
}

When the response type is an array, you must indicate it using the array bracket notation ([ ]) around the type or set isArray to true. GET /users response is an array of User annotation looks like this:

@ApiOkResponse({ type: [User] })

@ApiOkResponse({ type: User, isArray: true })

You'll see the endpoints with the new response type of User.

CreateUserDto properties with name optional

Swagger CLI Plugin

Exposing the properties and responses to Swagger results in additional boilerplate. Nest commes with a Swagger CLI Plugin to reduce boilerplate in your .dto.ts and .entity.ts files. Enable the plugin in your nest-cli.json file.

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/swagger"]
  }
}

Before: User entity, CreateUserDto and UsersController with boilerplate.

export class User {
  @ApiProperty()
  email: string;
  password: string;
  @ApiProperty({ required: false, nullable: true })
  name?: string | null;
}
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
  @ApiProperty()
  email: string;
  @ApiProperty()
  password: string;
  @ApiProperty({ required: false, nullable: true })
  name?: string | null;
}
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';

@Controller('users')
@ApiTags('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  @ApiCreatedResponse({ type: User })
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Get()
  @ApiOkResponse({ type: [User] })
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  @ApiOkResponse({ type: User })
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id);
  }

  @Patch(':id')
  @ApiCreatedResponse({ type: User })
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(+id, updateUserDto);
  }

  @Delete(':id')
  @ApiOkResponse({ type: User })
  remove(@Param('id') id: string) {
    return this.usersService.remove(+id);
  }
}

After: CLI plugin enabled and without boilerplate. You need to add @ApiHideProperty otherwise the plugin will also expose the password property.

import { ApiHideProperty } from '@nestjs/swagger';

export class User {
  email: string;
  @ApiHideProperty()
  password: string;
  name?: string | null;
}
export class CreateUserDto {
  email: string;
  password: string;
  name?: string | null;
}
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiTags } from '@nestjs/swagger';
import { User } from './entities/user.entity';

@Controller('users')
@ApiTags('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto): User {
    return this.usersService.create(createUserDto);
  }

  @Get()
  findAll(): User[] {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string): User {
    return this.usersService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto): User {
    return this.usersService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string): User {
    return this.usersService.remove(+id);
  }
}

Advanced Swagger Types

Check out the following posts for type-safe file uploads and download.

This allows you to directly test file upload and download in your Swagger documentation and is perfect for client generation tools to pick up the correct input values for the file(s).

Sponsor us

Did you find this post useful? We at notiz.dev write about our experiences developing Apps, Websites and APIs and develop Open Source tools. Your support would mean a lot to us 🙏. Receive a reward by sponsoring us on Patreon or start with a one-time donation on GitHub Sponsors.

Table of Contents

Top of Page Comments Related Articles

Related Posts

Find more posts like this one.

Authors
Marc Stammerjohann
October 17, 2022

Codegen REST API types and requests for Angular

Automatic code generation from OpenAPI 3 for Angular
Angular NestJS Read More
Authors
Marc Stammerjohann
July 27, 2022

Downloading files with NestJS

Setup type-safe endpoints for downloading files in your NestJS application.
NestJS Read More
Authors
Marc Stammerjohann
July 08, 2022

Maizzle: Craft beautiful HTML emails with Tailwind CSS

Send beautiful HTML emails via NestJS crafted with Maizzle and Tailwind CSS
Maizzle Tailwind CSS NestJS Read More
Authors
Marc Stammerjohann
August 26, 2021

NestJS: Type-safe File Uploads

Learn how to apply Swagger decorators for type-safe file upload endpoints.
NestJS Read More
Authors
Marc Stammerjohann
July 08, 2022

Send Emails with NestJS

Create Email Templates and send them with nodemailer from your Nest application
NestJS Read More
Authors
Marc Stammerjohann
September 12, 2022

Introducing NestJS Prisma Library and Schematics

Library and schematics to add Prisma integration to a NestJS application
NestJS Prisma Read More
Authors
Marc Stammerjohann
November 09, 2021

Dockerizing a NestJS app with Prisma and PostgreSQL

How to dockerize a NestJS application with Prisma and PostgreSQL.
NestJS Prisma Docker Read More
Authors
Marc Stammerjohann
April 07, 2020

GraphQL Code-First Approach with NestJS 7

Create a GraphQL API using Code-First Approach with NestJS 7.
NestJS GraphQL Prisma Read More

Sign up for our newsletter

Sign up for our newsletter to stay up to date. Sent every other week.

We care about the protection of your data. Read our Privacy Policy.