Nestjs - OpenApi Example

05/19/2021, Wed
Categories: #api

Documenting Apis

Nestjs provides a more opinionated way in expressing structure when developing a backend for your Node.js application, and this prescribed way of doing things coupled with the usage of TypeScript allows for better maintainable code.

As stated in their project README.md:

Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.

It is a bit ironic that the front end framework, Angular, was not well-received due to its break of compatibility with Angularjs and also being hampered by competition from other simpler front end frameworks. Yet, Nestjs's core ideals of structuring code into modules and using dependency injection has been successfully implemented, garnering wide adoption.

OpenApi, which was formerly known as Swagger, is a specification standard for describing JSON REST Apis. OpenApi uses a single YAML or JSON file to describe the entirety of an API, so all route resources are detailed in the file. This OpenApi file is usually parsed and a documentation site is generated from it.

Generally, the server code is annotated with structured comments which then a CLI tool is run to process the code to generate the OpenApi file.

With some background information on Nestjs and OpenApi, we can move onto the example of using OpenApi with Nestjs.

Create a folder for the Nestjs project

mkdir nestjs-open-api

Navigate into the directory

cd nestjs-open-api

Install the Nestjs CLI locally so the npx can be used later on as to avoid a global Nestjs install.

npm i @nestjs/cli

Generate a new project folder inside our 'nestjs-open-api' folder, as this folder will be the one where the OpenApi example will reside in.

npx nest new my-nest-project
cd my-nest-project

Install additional dependencies because the default Nestjs install does not include the OpenApi modules

# Other required dependencies since the default Nestjs
# install does not include them
npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata

# OpenApi modules
npm i --save @nestjs/swagger swagger-ui-express class-validator

With the project files in place, it is time to generate a resource for the OpenApi example, since a URL route corresponds to a resource. Nestjs comes with a useful scaffolding tool which we can use:

nest g resource users

In the above, we have generated a couple of Users URL routes, which will allow for creating, reading, updating and deleting users.

Now the bootstrap configuration of the Nestjs application will need to register the OpenApi modules to properly render the documentation page.

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

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

  const config = new DocumentBuilder()
    .setTitle('Users example')
    .setDescription('The users API description')
    .setVersion('1.0')
    .addTag('users')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

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

Run the development server by

npm start

You will be able to navigate to the following URL to preview the documentation in the browser

// Swagger documentation url
http://localhost:3000/api/

If you were to interact with the API on the OpenApi page, the response for the API requests will return hard coded strings. These are placeholder routes which need to be updated with Users information.

Stop the server and proceed to update the generated files.

The "user.entity.ts" file will be the location of interest because the code annotation must be placed here for the page to generate the OpenApi documentation. For our user resource object, we are going to have a "name", "age", and "gender" for this example's sake.

// src/users/entities/user.entity.ts
import { ApiProperty } from '@nestjs/swagger';

export class User {
    /**
     * The name of the User
     * @example Bob
     */
    name: string;

    @ApiProperty({ example: 1, description: 'The age of the person' })
    age: number;

    @ApiProperty({
        example: 'Male',
        description: 'The gender of the person',
    })
    gender: string;
}

The DTO, which will be the shape for the data during transport, will need annotations to be defined for matching up to what is described in the "user.entity.ts".

//src/users/dto/create-user.dto.ts

import { IsInt, IsString } from 'class-validator';

export class CreateUserDto {
    @IsString()
    readonly name: string;

    @IsInt()
    readonly age: number;

    @IsString()
    readonly gender: string;
}

The Users service will provide the methods for processing the user data. Normally, a proper database will be configured to store and retrieve data in a production application, but for our example, striving for simplicity, will lead us to use in-memory JavaScript objects for persistence. The data will only last for as long as the development server is kept running.

//src/users/users.service.ts

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {

  private users: User[] = [];

  // Add the user
  create(user: CreateUserDto) {
    this.users.push(user);
    return user;
  }

  // Find all users
  findAll() {
    return this.users;
  }

  // Find a specific user
  findOne(id: number) {
    return this.users[id];
  }

  // Update an user
  update(id: number, updateUser: UpdateUserDto) {
    return Object.assign(this.users[id], updateUser);
  }

  // Remove the user
  remove(id: number) {
    const newRemovedUserSet = this.users
      .filter((v, i) => {
        if (i !== id) return v;
      });
    this.users = newRemovedUserSet;
    return this.users;
  }
}

Restart the development server and go to the OpenApi documentation page and try adding a new user using the POST method with the following data in the Request Body to store a single user for interaction:

{
    "name": "Bob",
    "age": 30,
    "gender": "male"
}

Then you will be able to read the data with the GET method along with updating (PUT) and deleting (DELETE) of the user resource.