Skip to content

ganesanarun/nest-openapi-code-generator

Repository files navigation

@snow-tzu/nest-openapi-code-generator

npm version build

Open API 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 :param format
  • 📁 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-generator

yarn

yarn add @snow-tzu/nest-openapi-code-generator

pnpm

pnpm add @snow-tzu/nest-openapi-code-generator

Quick 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: 50

2. Generate Code

Using CLI

npx openapi-generate

Using 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.js

CLI 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.ts

Path 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: string

Generated 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: getUserPost

Generated:

@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:

  1. Create the templates' directory:

    templates/
    ├── controller.hbs
    ├── dto.hbs
    └── types.hbs
    
  2. Configure 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.yaml

Each 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.yaml

2. 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: 120

4. 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: deleteUser

Troubleshooting

Common Issues

1. "Cannot find module" errors

Make sure you've installed all peer dependencies:

yarn install @nestjs/common @nestjs/swagger class-validator class-transformer

2. 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-generate

4. 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-generate

Or programmatically:

import {Logger, LogLevel} from '@snow-tzu/nest-openapi-code-generator';

const logger = new Logger();
logger.setLevel(LogLevel.DEBUG);

Development Setup

  1. Clone the repository:

    git clone https://github.com/ganesanarun/nest-openapi-code-generator.git
    cd nest-openapi-code-generator
  2. Install dependencies:

    yarn install
  3. Run tests:

    yarn test
  4. Build the project:

    yarn run build

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

About

Contract-first OpenAPI code generator for NestJS with validation

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages