9

does anyone know of a way(either official or through a 3rd-party tool) to generate swagger json files without needing the nest js server to run?

I have a nest js application with controller routes and DTO's annotated with @nest/swagger decorators for documentation. I know I can get the swagger json files by starting the server and visiting /api-json but I need the ability to generate this file without having to start the server first.

2 Answers 2

7

I managed to generate a swagger file from my e2e tests without starting the server.

The code below generates a swagger spec in a *.json file that you can paste into https://editor.swagger.io/

// my-api.e2e-spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { HttpModule } from '@nestjs/axios';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import * as fs from 'fs';

describe('My E2E Tests', () => {
  let app: NestFastifyApplication;

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [HttpModule],
    }).compile();

    app = module.createNestApplication(new FastifyAdapter());
    app.setGlobalPrefix('/api/v1');
    await app.init();
    await app.getHttpAdapter().getInstance().ready();
  });

  afterAll(async () => {
    await app.close();
  });

  it('should generate swagger spec', async () => {
    const config = new DocumentBuilder().setTitle('My API').setDescription('My API').setVersion('1.0').build();

    const document = SwaggerModule.createDocument(app, config);
    fs.writeFileSync('./swagger.json', JSON.stringify(document));
  });
});

Note: My version of @nestjs/swagger in my package.json is 5.2.0

Sign up to request clarification or add additional context in comments.

Comments

0

In my case, PostgreSQL datasource was a dependency, so I eventually decided to use memory database, pg-mem to run on e2e test. Here is how I achieved the swagger.json export.

src/app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [generalConfig],
    }),
    TypeOrmModule.forRootAsync({
      name: 'default',
      imports: [ConfigModule, SharedModule],
      useClass: DatabaseConfig,
    }),
    SharedModule,
    InfoModule,
  ],
})
export class AppModule {}

test/swagger.e2e-spec.ts

import request from 'supertest';
import * as path from 'path';
import { writeFileSync } from 'fs';
import { DataType, newDb } from 'pg-mem';
import { DataSource } from 'typeorm';
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';

describe('SwaggerController (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const db = newDb();
    db.public.registerFunction({
      name: 'current_database',
      args: [],
      returns: DataType.text,
      implementation: () => 'localdb',
    });
    db.public.registerFunction({
      name: 'version',
      args: [],
      returns: DataType.text,
      implementation: () => '1',
    });
    // Get PG in memory DB connection
    const datasource = (await db.adapters.createTypeormDataSource({
      type: 'postgres',
      autoLoadEntities: true,
      synchronize: true,
    })) as any;
    await datasource.initialize();
    await datasource.synchronize();

    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(DataSource)
      .useValue(datasource)
      .compile();

    app = moduleFixture.createNestApplication();

    const config = new DocumentBuilder()
      .setTitle('NestJS App')
      .setDescription('NestJS App description')
      .setVersion('1.0')
      .build();
    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('docs', app, document);

    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  it('/docs (GET)', () => {
    return request(app.getHttpServer()).get('/docs').expect(200);
  });

  it('/docs-json (GET)', () => {
    return request(app.getHttpServer())
      .get('/docs-json')
      .expect(200)
      .expect((res) => {
        const swaggerJson = JSON.stringify(res.body, null, 2);
        const outputPath = path.resolve(process.cwd(), 'swagger.json');
        writeFileSync(outputPath, swaggerJson, { encoding: 'utf8' });
      });
  });
});

If you rely on "@nestjs/swagger plugin at nest-cli.json such as for schema definition property generation, the config is not applied on e2e execution by default as ts-jest compiles your source code files on the fly, in memory (Ref: https://docs.nestjs.com/openapi/cli-plugin#integration-with-ts-jest-e2e-tests) So you need to create a config file below

test/swagger.transformer.config.js

const transformer = require('@nestjs/swagger/plugin');

module.exports.name = 'nestjs-swagger-transformer';
// you should change the version number anytime you change the configuration below - otherwise, jest will not detect changes
module.exports.version = 1;

module.exports.factory = (cs) => {
  return transformer.before(
    {
      // @nestjs/swagger/plugin options (can be empty)
    },
    cs.program, // "cs.tsCompiler.program" for older versions of Jest (<= v27)
  );
};

and update jest-e2e.json in transform block

{
  ...
  "transform": {
    "^.+\\.(t|j)s$": [
      "ts-jest",
      {
        "astTransformers": {
          "before": ["./test/swagger.transformer.config.js"]
        }
      }
    ]
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.