@snow-tzu/nest-openapi-code-generator
v0.0.14
Published
Contract-first OpenAPI code generator for NestJS with validation
Maintainers
Readme
@snow-tzu/nest-openapi-code-generator

A contract-first OpenAPI code generator for NestJS applications that automatically generates controllers, DTOs, and type definitions from OpenAPI 3.1 specifications with built-in validation.
Features
- 🚀 Contract-First Development: Generate NestJS code from OpenAPI specifications
- 🔍 OpenAPI 3.1 Support: Full compatibility with the latest OpenAPI specification
- 🛡️ Automatic Validation: Built-in class-validator decorators for request/response validation
- 🔄 Path Parameter Conversion: Automatic conversion from OpenAPI
{param}to NestJS:paramformat - 📁 File Watching: Automatic regeneration when OpenAPI specs change
- 🎯 TypeScript First: Full TypeScript support with comprehensive type definitions
- 🔧 Configurable: Flexible configuration options for different project needs
- 📦 Zero Dependencies: Works out of the box with minimal setup
Installation
npm
npm install @snow-tzu/nest-openapi-code-generatoryarn
yarn add @snow-tzu/nest-openapi-code-generatorpnpm
pnpm add @snow-tzu/nest-openapi-code-generatorQuick Start
1. Create an OpenAPI Specification
Create a file specs/user.openapi.yaml:
openapi: 3.1.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
operationId: getUsers
summary: Get all users
responses:
'200':
description: List of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
operationId: createUser
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- email
- firstName
- lastName
properties:
id:
type: string
format: uuid
email:
type: string
format: email
firstName:
type: string
minLength: 1
maxLength: 50
lastName:
type: string
minLength: 1
maxLength: 50
CreateUserRequest:
type: object
required:
- email
- firstName
- lastName
properties:
email:
type: string
format: email
firstName:
type: string
minLength: 1
maxLength: 50
lastName:
type: string
minLength: 1
maxLength: 502. Generate Code
Using CLI
npx openapi-generateUsing Node.js API
import {generateFromConfig} from '@snow-tzu/nest-openapi-code-generator';
await generateFromConfig({
specsDir: './specs',
outputDir: './src/generated'
});3. Generated Output
The generator will create:
Controllers (src/generated/user/user.controller.base.ts):
import {
Get, Post, Put, Patch, Delete,
Body, Param, Query, Headers, HttpCode
} from '@nestjs/common';
import {ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery, ApiHeader} from '@nestjs/swagger';
import {CreateUserRequestDto, UserDto} from './user.dto';
@ApiTags('users')
export abstract class UserControllerBase {
// Decorated method with all NestJS annotations
@Get('/users')
@ApiOperation({summary: 'Get all users'})
@ApiResponse({status: 200, type: [UserDto]})
_getUsers(): Promise<UserDto[]> {
return this.getUsers();
}
// Abstract method for your implementation (no decorators)
abstract getUsers(): Promise<UserDto[]>;
@Post('/users')
@ApiOperation({summary: 'Create a new user'})
@ApiResponse({status: 201, type: UserDto})
_createUser(
@Body() body: CreateUserRequestDto
): Promise<UserDto> {
return this.createUser(body);
}
abstract createUser(body: CreateUserRequestDto): Promise<UserDto>;
}Implementation (src/modules/user/user.controller.ts):
import {Controller} from '@nestjs/common';
import {UserControllerBase} from '../../generated/user/user.controller.base';
import {CreateUserRequestDto, UserDto} from '../../generated/user/user.dto';
import {UserService} from './user.service';
@Controller('users') // Add @Controller here for dependency injection
export class UserController extends UserControllerBase {
constructor(private readonly userService: UserService) {
super();
}
// Clean implementation without decorators - just business logic
async getUsers(): Promise<UserDto[]> {
return this.userService.findAll();
}
async createUser(body: CreateUserRequestDto): Promise<UserDto> {
return this.userService.create(body);
}
}Key Benefits of the Override Pattern:
- 🎯 Clean Separation: Framework concerns (decorators) are separated from business logic
- 🔧 Easy Override: Just implement the abstract methods without worrying about decorators
- 🚀 Dependency Injection: Add
@Controller()to your implementation class for proper DI - 📝 Type Safety: Both methods have identical signatures ensuring type safety
DTOs (src/generated/dtos/user.dto.ts):
import {IsString, IsEmail, IsUUID, MinLength, MaxLength, IsNotEmpty} from 'class-validator';
import {ApiProperty} from '@nestjs/swagger';
export class User {
@ApiProperty({format: 'uuid'})
@IsUUID()
id: string;
@ApiProperty({format: 'email'})
@IsEmail()
email: string;
@ApiProperty({minLength: 1, maxLength: 50})
@IsString()
@MinLength(1)
@MaxLength(50)
@IsNotEmpty()
firstName: string;
@ApiProperty({minLength: 1, maxLength: 50})
@IsString()
@MinLength(1)
@MaxLength(50)
@IsNotEmpty()
lastName: string;
}
export class CreateUserRequest {
@ApiProperty({format: 'email'})
@IsEmail()
email: string;
@ApiProperty({minLength: 1, maxLength: 50})
@IsString()
@MinLength(1)
@MaxLength(50)
@IsNotEmpty()
firstName: string;
@ApiProperty({minLength: 1, maxLength: 50})
@IsString()
@MinLength(1)
@MaxLength(50)
@IsNotEmpty()
lastName: string;
}CLI Usage
Basic Commands
# Generate from default configuration
npx openapi-generate
# Specify custom paths
npx openapi-generate --specs ./api-specs --output ./src/api
# Watch for changes
npx openapi-generate --watch
# Use custom config file
npx openapi-generate --config ./openapi.config.jsCLI Options
| Option | Alias | Description | Default |
|------------|-------|-------------------------|---------------------|
| --config | -c | Path to config file | openapi.config.js |
| --specs | -s | Path to specs directory | ./specs |
| --output | -o | Output directory | ./src/generated |
| --watch | -w | Watch for changes | false |
Configuration
Configuration File
Create openapi.config.js in your project root:
module.exports = {
specsDir: './specs',
outputDir: './src/generated',
generateControllers: true,
generateDtos: true,
generateTypes: true,
generatorOptions: {
useSingleRequestParameter: false,
additionalProperties: {
// Custom properties for templates
}
},
vendorExtensions: {
'x-controller-name': 'controllerName'
}
};TypeScript Configuration
For TypeScript projects, create openapi.config.ts:
import {GeneratorConfig} from '@snow-tzu/nest-openapi-code-generator';
const config: GeneratorConfig = {
specsDir: './specs',
outputDir: './src/generated',
generateControllers: true,
generateDtos: true,
generateTypes: true,
generatorOptions: {
useSingleRequestParameter: false
}
};
export default config;Configuration Options
| Option | Type | Description | Default |
|--------------------------------------------------|-----------|-----------------------------------------------------|-------------------|
| specsDir | string | Directory containing OpenAPI specs | ./specs |
| outputDir | string | Output directory for generated code | ./src/generated |
| generateControllers | boolean | Generate NestJS controllers | true |
| generateDtos | boolean | Generate DTO classes | true |
| generateTypes | boolean | Generate TypeScript types | true |
| templateDir | string | Custom template directory | undefined |
| generatorOptions.useSingleRequestParameter | boolean | Use single parameter for request body | false |
| generatorOptions.includeErrorTypesInReturnType | boolean | Include error response types in method return types | false |
| vendorExtensions | object | Custom vendor extension mappings | {} |
Programmatic API
Basic Usage
import {
generateFromConfig,
GeneratorOrchestrator,
ConfigLoader
} from '@snow-tzu/nest-openapi-code-generator';
// Quick generation with default config
await generateFromConfig();
// Custom configuration
await generateFromConfig({
specsDir: './my-specs',
outputDir: './src/api'
});
// Advanced usage with orchestrator
const configLoader = new ConfigLoader();
const config = await configLoader.loadConfig();
const orchestrator = new GeneratorOrchestrator(config);
await orchestrator.generate();Parsing OpenAPI Specs
import {parseSpec, SpecParser} from '@snow-tzu/nest-openapi-code-generator';
// Quick parsing
const spec = await parseSpec('./specs/user.openapi.yaml');
// Advanced parsing with custom parser
const parser = new SpecParser();
const spec = await parser.parseSpec('./specs/user.openapi.yaml');File Watching
import {SpecWatcher} from '@snow-tzu/nest-openapi-code-generator';
const watcher = new SpecWatcher({
specsDir: './specs',
outputDir: './src/generated'
});
await watcher.start();
// Stop watching
watcher.stop();Naming Conventions & Code Generation Patterns
File Naming Conventions
The generator follows specific naming conventions based on your OpenAPI specification file names:
Spec File Names → Generated Class Names
| Spec File | Generated Controller Class | Generated DTO File |
|---------------------------------|---------------------------------|---------------------------|
| user.openapi.yaml | UserControllerBase | user.dto.ts |
| user.query.openapi.yaml | UserQueryControllerBase | user.query.dto.ts |
| order-management.openapi.yaml | OrderManagementControllerBase | order-management.dto.ts |
| api.v1.users.openapi.yaml | ApiV1UsersControllerBase | api.v1.users.dto.ts |
The generator automatically:
- Splits file names on dots (
.), hyphens (-), and underscores (_) - Capitalizes each part using PascalCase
- Joins them together for the class name
Directory Structure
Generated files are organized by resource name:
src/generated/
├── user/
│ ├── user.controller.base.ts
│ └── user.dto.ts
├── user.query/
│ ├── user.query.controller.base.ts
│ └── user.query.dto.ts
└── order-management/
├── order-management.controller.base.ts
└── order-management.dto.tsPath Parameter Handling
The generator automatically converts OpenAPI path parameter format to NestJS format:
Path Parameter Conversion
OpenAPI specifications use curly braces {paramName} for path parameters, while NestJS uses colons :paramName. The generator handles this conversion automatically:
OpenAPI Specification:
paths:
/users/{userId}:
get:
operationId: getUserById
parameters:
- name: userId
in: path
required: true
schema:
type: stringGenerated Controller:
@Get('/users/:userId') // Automatically converted to NestJS format
@ApiParam({ name: 'userId', type: String })
_getUserById(
@Param('userId') userId: string
): Promise<UserDto> {
return this.getUserById(userId);
}Multiple Path Parameters
The conversion works seamlessly with multiple path parameters:
OpenAPI:
/users/{userId}/posts/{postId}:
get:
operationId: getUserPostGenerated:
@Get('/users/:userId/posts/:postId')
_getUserPost(
@Param('userId') userId: string,
@Param('postId') postId: string
): Promise<PostDto> {
return this.getUserPost(userId, postId);
}Parameter Name Preservation
The generator preserves exact parameter names including:
- CamelCase:
{userId}→:userId - Underscores:
{user_id}→:user_id - Numbers:
{user_id_123}→:user_id_123 - Mixed formats:
{api_version}→:api_version
Advanced Features
Custom Templates
You can provide custom Handlebars templates for code generation:
Create the templates' directory:
templates/ ├── controller.hbs ├── dto.hbs └── types.hbsConfigure the template directory:
module.exports = { templateDir: './templates', // ... other options };
Vendor Extensions
Support for custom OpenAPI vendor extensions:
# In your OpenAPI spec
paths:
/users:
get:
x-controller-name: UserManagement
x-custom-decorator: '@CustomDecorator()'// In your config
module.exports = {
vendorExtensions: {
'x-controller-name': 'controllerName',
'x-custom-decorator': 'customDecorator'
}
};Multiple Spec Files
The generator automatically processes all OpenAPI files in the specs directory:
specs/
├── user.openapi.yaml
├── product.openapi.json
├── order.openapi.yml
└── inventory.openapi.yamlEach spec file generates its own set of controllers and DTOs.
Integration with NestJS
1. Extend Generated Controller Base Class
// user.service.ts
import {Injectable} from '@nestjs/common';
import {User, CreateUserRequest} from './generated/dtos';
@Injectable()
export class UserService {
async getUsers(): Promise<User[]> {
// Your implementation
return [];
}
async createUser(request: CreateUserRequest): Promise<User> {
// Your implementation
return {} as User;
}
}// user.controller.ts (extend generated controller)
import {Controller} from '@nestjs/common';
import {UserControllerBase} from './generated/user/user.controller.base';
import {UserService} from './user.service';
import {CreateUserRequestDto, UserDto} from './generated/user/user.dto';
@Controller() // Add @Controller for dependency injection
export class UserController extends UserControllerBase {
constructor(private userService: UserService) {
super();
}
// Clean implementation without decorators
async getUsers(): Promise<UserDto[]> {
return this.userService.getUsers();
}
async createUser(body: CreateUserRequestDto): Promise<UserDto> {
return this.userService.createUser(body);
}
}2. Import Implemented Controller
// app.module.ts
import {Module} from '@nestjs/common';
import {UserController} from './controllers/user.controller';
@Module({
controllers: [UserController],
// ... other module configuration
})
export class AppModule {
}3. Validation Pipeline
The generated DTOs work seamlessly with NestJS validation:
// main.ts
import {ValidationPipe} from '@nestjs/common';
import {NestFactory} from '@nestjs/core';
import {AppModule} from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
await app.listen(3000);
}
bootstrap();Best Practices
1. Organize Your Specs
specs/
├── common/
│ ├── errors.yaml
│ └── pagination.yaml
├── user/
│ └── user.openapi.yaml
├── product/
│ └── product.openapi.yaml
└── order/
└── order.openapi.yaml2. Use References
# specs/common/errors.yaml
components:
schemas:
Error:
type: object
properties:
code:
type: string
message:
type: string
# specs/user/user.openapi.yaml
openapi: 3.1.0
# ... other content
components:
schemas:
# Reference common schemas
Error:
$ref: '../common/errors.yaml#/components/schemas/Error'3. Validation Best Practices
# Use comprehensive validation in your schemas
CreateUserRequest:
type: object
required:
- email
- firstName
- lastName
properties:
email:
type: string
format: email
maxLength: 255
firstName:
type: string
minLength: 1
maxLength: 50
pattern: '^[a-zA-Z\s]+$'
age:
type: integer
minimum: 13
maximum: 1204. Use Meaningful Operation IDs
paths:
/users:
get:
operationId: getUsers # Becomes method name
post:
operationId: createUser
/users/{id}:
get:
operationId: getUserById
put:
operationId: updateUser
delete:
operationId: deleteUserTroubleshooting
Common Issues
1. "Cannot find module" errors
Make sure you've installed all peer dependencies:
yarn install @nestjs/common @nestjs/swagger class-validator class-transformer2. Validation is not working
Ensure you have the ValidationPipe configured:
app.useGlobalPipes(new ValidationPipe());3. Generated files not updating
Try clearing the output directory and regenerating:
rm -rf src/generated
npx openapi-generate4. TypeScript compilation errors
Check that your tsconfig.json includes the generated files:
{
"include": [
"src/**/*",
"src/generated/**/*"
]
}Debug Mode
Enable debug logging:
DEBUG=openapi-generator npx openapi-generateOr programmatically:
import {Logger, LogLevel} from '@snow-tzu/nest-openapi-code-generator';
const logger = new Logger();
logger.setLevel(LogLevel.DEBUG);Development Setup
Clone the repository:
git clone https://github.com/ganesanarun/nest-openapi-code-generator.git cd nest-openapi-code-generatorInstall dependencies:
yarn installRun tests:
yarn testBuild the project:
yarn run build
License
This project is licensed under the MIT License - see the LICENSE file for details.
