Nx NestJs - How to autogenerate OpenApi/Swagger specs
Tags: nestjs, nx, openapi, javascript
Published: 2022-05-08 11:06:39

Hello everyone, short article this time. 
As I started using NestJs with Nx I noticed a point of contingency, there are still debates on how to activate OpenApi/Swagger in NestJs with the auto documentation option. 

Fear no more, at the end of this tutorial that problem will be solved.

## Versions used
I am using the latest Nx at the time of writing, which is V14, with NestJs 8.0 and NestJs Swagger 5.2.

## Packages 
Install the required package first, if you use express install `npm install --save @nestjs/swagger swagger-ui-express` otherwise, for fastify use `npm install --save @nestjs/swagger fastify-swagger`

## Add Swagger Module
In `main.ts` from your NestJs' project folder add the following:

```typescript
function setupOpenApi(app: INestApplication) {
  const config = new DocumentBuilder().setTitle('API Documentation').setVersion('1.0').addTag('api').build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);
}
```
then in the `bootstrap` function just call the `setupOpenApi` function.

My entire `main.ts` looks like this, take note that I prefered the following options:
- I used Fastify, because I don't have any hard dependency on an express middleware and it improves the performance
- I Used a global prefix, thus the I had to set `useGlobalPrefix:true` in the function above
- The global prefix I used is the default `api` that conflicts with the default route for OpenApi which in the documentation is coincidently `api`. If you keep them like this without adding the `useGlobalPrefix: true` flag then you will be able to access both the APIs and the OpenAPI on `http://localhost:3000/api` but you will notice that the documentation is wrong as it doesn't contain the `globalPrefix` used.
To fix this we will have to set the `useGlobalPrefix: true` flag for OpenAPI and access the documentation at `http://localhost:3000/api/api` which looks weird so I customized it to `http://localhost:3000/api/openApi`
Working example bellow:
```typescript
/**
 * This is not a production server yet!
 * This is only a minimal backend to get started.
 */
import { INestApplication, Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

import { AppModule } from './app/app.module';

async function bootstrap() {
  const fastifyOptions: ConstructorParameters<typeof FastifyAdapter>[0] = {
    logger: true,
  };
  const fastifyAdapter = new FastifyAdapter(fastifyOptions);
  const app = await NestFactory.create<NestFastifyApplication>(AppModule, fastifyAdapter);

  const globalPrefix = 'api';
  app.setGlobalPrefix(globalPrefix);
  setupOpenApi(app);

  const port = process.env.PORT || 3333;
  await app.listen(port);
  Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`);
}

bootstrap();

function setupOpenApi(app: INestApplication) {
  const config = new DocumentBuilder().setTitle('API Documentation').setVersion('1.0').addTag('api').build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('openApi', app, document, { useGlobalPrefix: true });
}

```

Right now you are ready to use the manual documentation part of the package, but we both know you are not here for that

## Auto Documentation

This will be rather short, we will have 2, non-hacky, ways to do this.

### Nest-Cli.json - Not suggested

Just create a `nest-cli.json` file at the root of the project, then add
```json
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "apps",
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/swagger/plugin",
        "options": {
          "dtoFileNameSuffix": [".entity.ts", ".dto.ts"],
          "controllerFileNameSuffix": [".controller.ts"],
          "classValidatorShim": true,
          "dtoKeyOfComment": "description",
          "controllerKeyOfComment": "description",
          "introspectComments": true
        }
      }
    ]
  }
}
```
Do not be scared of the above settings, they are just the defaults to be more explicit. What is really changed is the `sourceRoot` which in the case on NX (at least for the presets with apps) is the folder `apps`. 

This should detect your parameters, and class names, but will most likely not see what is inside your classes and they will appear empty. 

### TsPlugin - Recommended, more customizable 
Go to your `project.json` in your NestJs' project folder or `angular.json` in the root folder for older version and search for the project you need. We will need to alter the `build` command. 

The entire build command is displayed bellow
```json
 "build": {
      "executor": "@nrwl/node:webpack",
      "outputs": ["{options.outputPath}"],
      "options": {
        "outputPath": "dist/apps/api",
        "main": "apps/api/src/main.ts",
        "tsConfig": "apps/api/tsconfig.app.json",
        "tsPlugins": [
          {
            "name": "@nestjs/swagger/plugin",
            "options": {
              "dtoFileNameSuffix": [".entity.ts", ".dto.ts"],
              "controllerFileNameSuffix": [".controller.ts"],
              "classValidatorShim": true,
              "dtoKeyOfComment": "description",
              "controllerKeyOfComment": "description",
              "introspectComments": true
            }
          }
        ],
        "assets": ["apps/api/src/assets"]
      },
```
As you can notice we added a custom Webpack TsPlugin as specified in the documentation above this https://docs.nestjs.com/openapi/cli-plugin#integration-with-ts-jest-e2e-tests.

As mentioned, this is the recommended approach, it will see all the classes from anywhere in the monorepo as long as they are imported into the project.

## Final details
Keep in mind the following:
- You can use only `class`es not `interface`es
- You need to append a `.dto.ts` or `.entity.ts` unless you change that

Thanks for reading!