Leverage OpenAPI to generate a strongly typed client in NestJS

Leverage OpenAPI to generate a strongly typed client in NestJS

I'll show you how to leverage a generated OpenAPI document to create a typescript client in NestJS that can be used in the front-end app.

Why would someone want to do that? Because it's more beneficial to have statically typed endpoints than to do the typing yourself. Also, auto-generating an OpenAPI document in a CI helps make sure everything is good to go at compile time.

More reasons you might want to generate a typescript client:

  • Generating the code automatically can save time and is less error-prone.

  • It's even more desirable to generate the code with TypeScript, because it means that requests and responses are fully typed, including your deeply structured domain objects. There is no need to tolerate any type.

  • The client and server are more likely to stay compatible since the approach is based on shared API specifications.

What is OpenAPI?

OpenAPI, also known as Swagger, is a specification for building and documenting RESTful APIs. It is based on JSON or YAML, making it easy to read and write. The specification defines a set of rules for describing API endpoints, operations, parameters, response formats, and authentication methods.

With OpenAPI, developers can:

  • Easily create API documentation that is consistent, up-to-date, and easy to understand.

  • Reduce confusion, improve communication between teams, and make it easier for external developers to integrate with your API.

What can OpenAPI tools do?

OpenAPI tools provide various functionalities to work with OpenAPI specifications. Some of the common tools and their functionalities are:

  • Documentation Generators: Documentation generators use OpenAPI specifications to generate interactive API documentation. These tools can help improve API adoption and reduce support requests by making it easy for developers to understand how to use an API.

  • Code Generators: Code generators use OpenAPI specifications to generate client libraries, server stubs, and other codes. These tools can help reduce development time and ensure consistency between client and server implementations.

  • Validators: Validators check OpenAPI specifications for compliance with the OpenAPI specification. These tools help ensure that specifications are valid, complete, and accurate.

  • Mock Servers: Mock servers use OpenAPI specifications to create simulated API endpoints that return predefined responses. These tools can help developers test their code against an API before it is fully implemented.

OpenAPI Tools can streamline the process of building and documenting the API, making it easier for developers to understand and consume services.

NPM package nest-openapi-tools

NestJS is a popular Node.js framework for building scalable and modular applications. Swagger can be integrated into NestS to automatically generate API documentation. A problem you may run into is that Swagger will generate the web server with specifications, but will not generate specification files. Also, typescript client generation may be not generated automatically.

The nest-openapi-tools package linked above makes it simply use NestJS with Swagger (OpenAPI). It also includes various tools that make developers' lives easier.

This package generates a YAML or JSON specification file and @openapitools/openapi-generator-cli to generate a client. It will also result in a new, fully-setup API client ready to go for our consuming layer.

Integrating nest-openapi-tools

Installation:

First, we need to install the necessary dependencies. In the NestJS project, run the following command:

npm install --save nest-openapi-tools @nestjs/swagger swagger-ui-express

swagger-ui-express adds a middleware to your express app to serve the Swagger UI bound to your Swagger document. @nestjs/swagger provides the properties of swagger.

Bootstrapping nest-openapi-tools

The next step is to bootstrap the nest-openapi-tools module in your application.

To do so, initialize OpenApiNestFactory using nest-openapi-tools in the main.ts file. See the below code:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { OpenApiNestFactory } from 'nest-openapi-tools';

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

  await OpenApiNestFactory.configure(app, 
    new DocumentBuilder()
      .setTitle('My API')
      .setDescription('An API to do awesome things')
      .addBearerAuth(),
    {
      webServerOptions: {
        enabled: true,
        path: 'api-docs',
      },
      fileGeneratorOptions: {
        enabled: true,
        outputFilePath: './openapi.yaml',  // or ./openapi.json
      },
      clientGeneratorOptions: {
        enabled: true,
        type: 'typescript-axios',
        outputFolderPath: '../typescript-api-client/src',
        additionalProperties:
          'apiPackage=clients,modelPackage=models,withoutPrefixEnums=true,withSeparateModelsAndApi=true',
        openApiFilePath: './openapi.yaml', // or ./openapi.json
        skipValidation: true, // optional, false by default
      },
    }, {
    operationIdFactory: (c: string, method: string) => method,
  });

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

The first thing you'll notice above is the OpenApiNestFactory. This simplifies the process of:

  1. Generating the OpenAPI file from a NestJS API

  2. Generating a client project (i.e. an Axios client or Angular client module)

  3. Starting up the OpenAPI documentation web server

The first one is the app then DocumentBuilder class.

Basically, DocumentBuilder helps structure a base document. This document confirms the OpenAPI specification. It allows you to set several properties such as title, description, etc. In the above example, the title, description, and authorization properties are set.

Then OpenApiToolsOptions, which provides various tools that will be helpful in building the API documentation. These are the different tool options available:

  1. webServerOptions: This option is for generating the URL for open API ie swagger-ui. Here, all the endpoints in the application along with the response and body can be seen. You can also add a path here that will be the end-point to view the UI.

  2. fileGeneratorOptions: In nestJS, open API generated the web server with specifications but did not currently generate specification files. This option generates a YAML or JSON specification file.

  3. clientGeneratorOptions: Here client project (i.e. an Axios client or an Angular client module) can be generated. By using this as part of the development experience, we can build our APIs with NestJS's npm run start:dev running and have a new, fully-setup API client ready to go for our consuming layer (whether this is a SPA app or another API service).

With SwaggerOptions, you can use different options for Swagger to make the user experience better.

Using Swagger in the NestJS application

There are a few questions that front-end developers might have:

  • Is this field required?

  • What kind of type is this property?

  • Are there any descriptions for this property in the text?

  • What are the error messages and error codes that are on the code?

  • What kind of response is the API giving?

Below are the steps to use swagger in the NestJS project

  1. Describe DTOs

    In NestJS, add the @ApiProperty decorator on properties for labeling.

     import { ApiProperty } from "@nestjs/swagger";
    
     export enum Status {
         START = 'start',
         COMPLETED = 'completed'
     };
    
     export enum TaskType {
         NONE = 'none',
         EMAIL = 'email'
     }
    
     export class TaskDto {
         @ApiProperty({
             type: Number,
             description: 'ID of the task'
         })
         id: number;
    
         @ApiProperty({
             enum: Object.values(Status),
             required: false
         })
         @IsEnum(Status)
         status: Status = Status.START
    
         @ApiProperty({
             enum: Object.values(TaskType),
             required: false
         })
         taskType: TaskType;
     }
    
  2. Describe the Payload and the Parameters of the API

    Now, add the dto decorator with @ApiProperty to the controller as shown below:

     @Controller('task')
     export class TaskController {
       ...,
    
       @Post()
       createTask(@Body() task: TaskDto): Observable<TaskDto[]> {'
         return this.taskService.CreateTask(task);
       }
     }
    
  3. Describe the Response

    There are a few common decorators for describing the response.

    • @ApiOkResponse

    • @ApiNotFoundResponse

    • @ApiUnauthorizedRTesponse

    import { 
        ApiNotFoundResponse, 
        ApiOkResponse, 
        ApiOperation, 
        ApiParam
    } from '@nestjs/swagger';


    export class TaskController {
        ...,

        @ApiOkResponse({
            description: 'OK',
            type: TaskDto,
        })
        @ApiNotFoundResponse({
            description: '404. NotFoundException. Task was not found',
        })
        @ApiUnauthorizedResponse({
          schema: {
            type: 'object',
            example: {
              message: 'string',
            },
          },
          description: '401. UnauthorizedException.',
        })
        @ApiOperation({ summary: 'Get task by id' })
        @ApiParam({ name: 'id'})
        @Get(':id')
        getTaskById(@Param('id') id): Promise<TaskDto> {
            return this.taskService.GetTaskById(id);
        }
    }

Find more on swagger integration with NestJS at this link.

Now that everything is set up, run the NestJS application**,** and the Swagger UI will be available /api-doc which you have defined in webServerOptions.

swagger ui

Note: Make sure to enable webServerOptions in main.ts file.

When running the NestJS application, if fileGeneratorOptions is enabled in main.ts file, YAML or JSON specification file will be automatically generated along with openapitools JSON file. On every change in the application file, this specification file will be updated.

Generating the typescript client file

Build the NestJS application with the command npm run start:dev and have a new, fully setup API client ready for your consuming layer. After starting the application, if clientGeneratorOptions is enabled in main.ts file it will generate the typescript client, which can be consumed by the frontend.

Below is the image of the typescript client generated by nest-openapi-tools

Note: File and client generation should be disabled in production as they are costly to startup time.

Meeting modern demands with OpenAPI

Together, NestJS and OpenAPI tools can be used to create robust and well-documented APIs. With the help of OpenAPI tools, developers can easily create API endpoints that can be used by clients, such as web or mobile applications.

Overall, Nest and OpenAPI tools make a powerful combination that can help developers meet the needs of their users with reliable and easy-to-maintain APIs that meet the demands of modern web and mobile applications.