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.
- 🚀 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
npm install @snow-tzu/nest-openapi-code-generatoryarn add @snow-tzu/nest-openapi-code-generatorpnpm add @snow-tzu/nest-openapi-code-generatorCreate 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: 50npx openapi-generateimport {generateFromConfig} from '@snow-tzu/nest-openapi-code-generator';
await generateFromConfig({
specsDir: './specs',
outputDir: './src/generated'
});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;
}# 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.js| 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 |
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'
}
};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;| 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 | {} |
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();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');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();The generator follows specific naming conventions based on your OpenAPI specification file 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
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.ts
The generator automatically converts OpenAPI path parameter format to NestJS format:
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);
}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);
}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
You can provide custom Handlebars templates for code generation:
-
Create the templates' directory:
templates/ ├── controller.hbs ├── dto.hbs └── types.hbs -
Configure the template directory:
module.exports = { templateDir: './templates', // ... other options };
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'
}
};The generator automatically processes all OpenAPI files in the specs directory:
specs/
├── user.openapi.yaml
├── product.openapi.json
├── order.openapi.yml
└── inventory.openapi.yaml
Each spec file generates its own set of controllers and DTOs.
// 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);
}
}// app.module.ts
import {Module} from '@nestjs/common';
import {UserController} from './controllers/user.controller';
@Module({
controllers: [UserController],
// ... other module configuration
})
export class AppModule {
}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();specs/
├── common/
│ ├── errors.yaml
│ └── pagination.yaml
├── user/
│ └── user.openapi.yaml
├── product/
│ └── product.openapi.yaml
└── order/
└── order.openapi.yaml
# 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'# 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: 120paths:
/users:
get:
operationId: getUsers # Becomes method name
post:
operationId: createUser
/users/{id}:
get:
operationId: getUserById
put:
operationId: updateUser
delete:
operationId: deleteUserMake sure you've installed all peer dependencies:
yarn install @nestjs/common @nestjs/swagger class-validator class-transformerEnsure you have the ValidationPipe configured:
app.useGlobalPipes(new ValidationPipe());Try clearing the output directory and regenerating:
rm -rf src/generated
npx openapi-generateCheck that your tsconfig.json includes the generated files:
{
"include": [
"src/**/*",
"src/generated/**/*"
]
}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);-
Clone the repository:
git clone https://github.com/ganesanarun/nest-openapi-code-generator.git cd nest-openapi-code-generator -
Install dependencies:
yarn install
-
Run tests:
yarn test -
Build the project:
yarn run build
This project is licensed under the MIT License - see the LICENSE file for details.
