Logonestjs-openapi
Advanced

Programmatic API

Call generate() from build scripts, tests and custom tooling to produce OpenAPI specs from NestJS code without the CLI.

Use the programmatic API to integrate spec generation into build scripts, test suites, and custom tooling.

Basic Usage

import {  } from 'nestjs-openapi';

const  = await ('./openapi.config.ts');

.(`Generated: ${.}`);
.(`Paths: ${.}`);
.(`Operations: ${.}`);
.(`Schemas: ${.}`);

Options

The generate() function accepts an optional second parameter for overrides:

import { generate } from 'nestjs-openapi';

const result = await generate('./openapi.config.ts', {
  format: 'yaml', // Override output format
  debug: true, // Enable verbose logging
});
OptionTypeDescription
format'json' | 'yaml'Override the output format
debugbooleanEnable debug logging to stdout

Build Script Examples

Simple Build Script

// scripts/generate-openapi.ts
import { generate } from 'nestjs-openapi';

async function main() {
  console.log('Generating OpenAPI specification...');

  const start = performance.now();
  const result = await generate('./openapi.config.ts');
  const duration = ((performance.now() - start) / 1000).toFixed(2);

  console.log(`Generated ${result.outputPath} in ${duration}s`);
  console.log(`  ${result.pathCount} paths`);
  console.log(`  ${result.operationCount} operations`);
  console.log(`  ${result.schemaCount} schemas`);
}

main().catch((error) => {
  console.error('Generation failed:', error.message);
  process.exit(1);
});

Run with:

npx tsx scripts/generate-openapi.ts

Multi-App Build Script

// scripts/generate-all.ts
import { generate } from 'nestjs-openapi';
import { resolve } from 'path';

interface AppConfig {
  name: string;
  configPath: string;
}

const apps: AppConfig[] = [
  { name: 'Backend API', configPath: 'apps/backend/openapi.config.ts' },
  { name: 'Admin API', configPath: 'apps/admin/openapi.config.ts' },
  { name: 'Public API', configPath: 'apps/public/openapi.config.ts' },
];

async function generateAll() {
  console.log('Generating OpenAPI specifications...\n');

  const results = await Promise.all(
    apps.map(async (app) => {
      try {
        const start = performance.now();
        const result = await generate(app.configPath);
        const duration = ((performance.now() - start) / 1000).toFixed(2);

        return {
          app: app.name,
          success: true,
          result,
          duration,
        };
      } catch (error) {
        return {
          app: app.name,
          success: false,
          error: error instanceof Error ? error.message : String(error),
        };
      }
    }),
  );

  // Print results
  for (const r of results) {
    if (r.success) {
      console.log(
        `${r.app}: ${r.result.operationCount} operations (${r.duration}s)`,
      );
    } else {
      console.error(`${r.app}: FAILED - ${r.error}`);
    }
  }

  // Exit with error if any failed
  const failed = results.filter((r) => !r.success);
  if (failed.length > 0) {
    process.exit(1);
  }
}

generateAll();

Watch Mode Script

// scripts/watch-openapi.ts
import { generate } from 'nestjs-openapi';
import { watch } from 'chokidar';

const CONFIG_PATH = './openapi.config.ts';
const WATCH_PATTERNS = ['src/**/*.ts', 'openapi.config.ts'];

async function regenerate() {
  console.log('\nRegenerating OpenAPI spec...');
  try {
    const result = await generate(CONFIG_PATH);
    console.log(`Generated ${result.operationCount} operations`);
  } catch (error) {
    console.error(
      'Generation failed:',
      error instanceof Error ? error.message : error,
    );
  }
}

// Initial generation
regenerate();

// Watch for changes
const watcher = watch(WATCH_PATTERNS, {
  ignoreInitial: true,
  ignored: ['**/node_modules/**', '**/*.spec.ts'],
});

watcher.on('change', (path) => {
  console.log(`File changed: ${path}`);
  regenerate();
});

console.log('Watching for changes...');

Integration Patterns

With Vite

// vite.config.ts
import { defineConfig, type Plugin } from 'vite';
import { generate } from 'nestjs-openapi';

function openapiPlugin(): Plugin {
  return {
    name: 'openapi-generate',
    async buildStart() {
      console.log('Generating OpenAPI spec...');
      await generate('./openapi.config.ts');
    },
  };
}

export default defineConfig({
  plugins: [openapiPlugin()],
});

With esbuild

// build.ts
import { build } from 'esbuild';
import { generate } from 'nestjs-openapi';

async function main() {
  // Generate OpenAPI spec first
  console.log('Generating OpenAPI spec...');
  await generate('./openapi.config.ts');

  // Then build
  console.log('Building application...');
  await build({
    entryPoints: ['src/main.ts'],
    bundle: true,
    outdir: 'dist',
    platform: 'node',
    format: 'esm',
  });
}

main();

With Jest/Vitest

// tests/openapi.test.ts
import { generate } from 'nestjs-openapi';
import { readFileSync } from 'fs';

describe('OpenAPI Specification', () => {
  let spec: any;

  beforeAll(async () => {
    await generate('./openapi.config.ts');
    spec = JSON.parse(readFileSync('openapi.json', 'utf-8'));
  });

  it('should have correct info', () => {
    expect(spec.info.title).toBe('My API');
    expect(spec.info.version).toBe('1.0.0');
  });

  it('should have expected endpoints', () => {
    expect(spec.paths['/api/users']).toBeDefined();
    expect(spec.paths['/api/users'].get).toBeDefined();
    expect(spec.paths['/api/users'].post).toBeDefined();
  });

  it('should have schemas for DTOs', () => {
    expect(spec.components.schemas.CreateUserDto).toBeDefined();
    expect(spec.components.schemas.UserDto).toBeDefined();
  });

  it('should have security schemes', () => {
    expect(spec.components.securitySchemes.bearerAuth).toBeDefined();
  });
});

With Semantic Release

// release.config.ts
import { generate } from 'nestjs-openapi';

export default {
  plugins: [
    '@semantic-release/commit-analyzer',
    '@semantic-release/release-notes-generator',
    [
      '@semantic-release/exec',
      {
        prepareCmd: `
        npx tsx -e "
          import { generate } from 'nestjs-openapi';
          await generate('./openapi.config.ts');
        "
      `,
      },
    ],
    [
      '@semantic-release/git',
      {
        assets: ['openapi.json'],
        message: 'chore(release): update OpenAPI spec [skip ci]',
      },
    ],
  ],
};

Error Handling

Basic Error Handling

import { generate } from 'nestjs-openapi';

try {
  const result = await generate('./openapi.config.ts');
  console.log('Success:', result.outputPath);
} catch (error) {
  if (error instanceof Error) {
    console.error('Failed:', error.message);
  }
  process.exit(1);
}

Typed Error Handling

import {  } from 'nestjs-openapi';
import {
  ,
  ,
  ,
  ,
} from 'nestjs-openapi';

try {
  await ('./openapi.config.ts');
} catch () {
  if ( instanceof ) {
    .('Config file not found');
  } else if ( instanceof ) {
    .('Failed to load config file');
  } else if ( instanceof ) {
    .('Invalid configuration');
  } else if ( instanceof ) {
    .(`Entry module not found: ${.}`);
  } else {
    throw ;
  }
  .(1);
}

Accessing Generated Spec

Read the generated spec after generation:

import { generate } from 'nestjs-openapi';
import { readFileSync } from 'fs';
import type { OpenApiSpec } from 'nestjs-openapi';

async function main() {
  const result = await generate('./openapi.config.ts');

  // Read the generated spec
  const spec: OpenApiSpec = JSON.parse(
    readFileSync(result.outputPath, 'utf-8'),
  );

  // Process the spec
  console.log('Endpoints:');
  for (const [path, methods] of Object.entries(spec.paths)) {
    for (const method of Object.keys(methods)) {
      console.log(`  ${method.toUpperCase()} ${path}`);
    }
  }
}

main();

See Also

On this page