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
});| Option | Type | Description |
|---|---|---|
format | 'json' | 'yaml' | Override the output format |
debug | boolean | Enable 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.tsMulti-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
- generate() - API reference
- Effect API - Effect-TS integration
- CI/CD Recipe - Automation patterns