tsoa provides a powerful extensibility mechanism that allows you to create custom route generators. This enables you to integrate tsoa with any Node.js framework or customize the route generation logic to match your specific needs.
Overview
Custom route generators extend the AbstractRouteGenerator class and implement the GenerateCustomRoutes method. This gives you full control over how routes are generated while still leveraging tsoa’s metadata generation capabilities.
The AbstractRouteGenerator provides helper methods for building models, properties, and parameters from the metadata generated by tsoa’s TypeScript analysis.
Creating a Custom Route Generator
Basic Structure
A custom route generator must extend AbstractRouteGenerator and implement the GenerateCustomRoutes method:
import { AbstractRouteGenerator , ExtendedRoutesConfig } from '@tsoa/cli' ;
import { Tsoa } from '@tsoa/runtime' ;
export class CustomRouteGenerator extends AbstractRouteGenerator < ExtendedRoutesConfig > {
constructor (
metadata : Tsoa . Metadata ,
options : ExtendedRoutesConfig
) {
super ( metadata , options );
}
public async GenerateCustomRoutes () : Promise < void > {
// Your custom route generation logic here
const context = this . buildContext ();
const models = this . buildModels ();
// Generate routes based on your framework's requirements
await this . generateRoutesFile ( context , models );
}
private async generateRoutesFile ( context : any , models : any ) : Promise < void > {
// Implement your custom file generation logic
}
}
The AbstractRouteGenerator provides access to complete metadata about your controllers:
public async GenerateCustomRoutes (): Promise < void > {
// Access controller metadata
this.metadata.controllers.forEach( controller => {
console . log ( `Controller: ${ controller . name } ` );
console . log ( `Path: ${ controller . path } ` );
console . log ( `Location: ${ controller . location } ` );
// Access method metadata
controller . methods . forEach ( method => {
console . log ( ` Method: ${ method . name } ` );
console . log ( ` HTTP Method: ${ method . method } ` );
console . log ( ` Path: ${ method . path } ` );
console . log ( ` Success Status: ${ method . successStatus } ` );
// Access parameter metadata
method . parameters . forEach ( param => {
console . log ( ` Parameter: ${ param . name } ` );
console . log ( ` Type: ${ param . type . dataType } ` );
console . log ( ` In: ${ param . in } ` );
});
});
});
}
Building Context
The buildContext() method provides a structured context object with all necessary information:
public async GenerateCustomRoutes (): Promise < void > {
const context = this . buildContext ();
// Context includes:
// - authenticationModule: Path to authentication module
// - iocModule: Path to IoC container module
// - basePath: Base path for all routes
// - controllers: Array of controller metadata
// - models: Generated model schemas
// - useSecurity: Whether any routes use security
// - useFileUploads: Whether any routes handle file uploads
// - multerOpts: Multer configuration for file uploads
}
Building Models
The buildModels() method generates runtime model schemas from TypeScript types:
public async GenerateCustomRoutes (): Promise < void > {
const models = this . buildModels ();
// models is a dictionary of TsoaRoute.Models
// Each model includes validation schemas for:
// - refObject: Object types with properties
// - refEnum: Enum types
// - refAlias: Type aliases
Object.keys(models).forEach( modelName => {
const model = models [ modelName ];
if ( model . dataType === 'refObject' ) {
console . log ( `Object Model: ${ modelName } ` );
console . log ( `Properties:` , Object . keys ( model . properties ));
console . log ( `Additional Properties:` , model . additionalProperties );
}
});
}
Configuration
Registering Your Custom Generator
Specify your custom route generator in tsoa.json:
{
"entryFile" : "src/server.ts" ,
"noImplicitAdditionalProperties" : "throw-on-extras" ,
"routes" : {
"routesDir" : "src/generated" ,
"routeGenerator" : "./src/generators/customRouteGenerator.ts"
},
"spec" : {
"outputDirectory" : "api-docs" ,
"specVersion" : 3
}
}
The routeGenerator path should point to a file that exports your custom generator class as the default export.
Using Custom Templates
For simpler customizations, you can use a custom Handlebars template:
{
"routes" : {
"routesDir" : "src/generated" ,
"middleware" : "express" ,
"middlewareTemplate" : "./templates/custom-express.hbs"
}
}
Advanced Examples
Example 1: Fastify Integration
import { AbstractRouteGenerator , ExtendedRoutesConfig } from '@tsoa/cli' ;
import { Tsoa } from '@tsoa/runtime' ;
import { writeFile } from 'fs/promises' ;
export class FastifyRouteGenerator extends AbstractRouteGenerator < ExtendedRoutesConfig > {
public async GenerateCustomRoutes () : Promise < void > {
const context = this . buildContext ();
const models = this . buildModels ();
let routeCode = `import { FastifyInstance } from 'fastify'; \n ` ;
routeCode += `import { TsoaRoute } from '@tsoa/runtime'; \n\n ` ;
// Import controllers
context . controllers . forEach ( controller => {
const importPath = this . getRelativeImportPath ( controller . location );
routeCode += `import { ${ controller . name } } from ' ${ importPath } '; \n ` ;
});
routeCode += ` \n export function RegisterRoutes(app: FastifyInstance) { \n ` ;
// Generate routes for each controller
context . controllers . forEach ( controller => {
controller . actions . forEach ( action => {
routeCode += ` app. ${ action . method } (' ${ action . fullPath } ', async (request, reply) => { \n ` ;
routeCode += ` const controller = new ${ controller . name } (); \n ` ;
routeCode += ` const result = await controller. ${ action . name } ( \n ` ;
// Map parameters
Object . keys ( action . parameters ). forEach (( paramName , index ) => {
const param = action . parameters [ paramName ];
let value = '' ;
switch ( param . in ) {
case 'path' :
value = `request.params[' ${ param . name } ']` ;
break ;
case 'query' :
value = `request.query[' ${ param . name } ']` ;
break ;
case 'body' :
value = `request.body` ;
break ;
case 'header' :
value = `request.headers[' ${ param . name } ']` ;
break ;
}
const comma = index < Object . keys ( action . parameters ). length - 1 ? ',' : '' ;
routeCode += ` ${ value }${ comma } \n ` ;
});
routeCode += ` ); \n ` ;
routeCode += ` reply.status( ${ action . successStatus || 200 } ).send(result); \n ` ;
routeCode += ` }); \n\n ` ;
});
});
routeCode += `} \n ` ;
const outputPath = ` ${ this . options . routesDir } /routes.ts` ;
await writeFile ( outputPath , routeCode );
}
}
Example 2: Custom Validation Logic
import { AbstractRouteGenerator , ExtendedRoutesConfig } from '@tsoa/cli' ;
import { Tsoa , TsoaRoute } from '@tsoa/runtime' ;
export class CustomValidationGenerator extends AbstractRouteGenerator < ExtendedRoutesConfig > {
public async GenerateCustomRoutes () : Promise < void > {
const context = this . buildContext ();
const models = this . buildModels ();
// Generate custom validation schemas
const validationSchemas = this . generateValidationSchemas ( models );
// Generate routes with custom validation
await this . generateRoutesWithValidation ( context , validationSchemas );
}
private generateValidationSchemas ( models : TsoaRoute . Models ) : Map < string , any > {
const schemas = new Map < string , any >();
Object . keys ( models ). forEach ( modelName => {
const model = models [ modelName ];
if ( model . dataType === 'refObject' ) {
// Convert to your preferred validation library schema
// (e.g., Zod, Yup, Joi)
const schema = {
type: 'object' ,
properties: {},
required: []
};
Object . keys ( model . properties ). forEach ( propName => {
const prop = model . properties [ propName ];
schema . properties [ propName ] = {
type: prop . dataType ,
required: prop . required
};
if ( prop . required ) {
schema . required . push ( propName );
}
});
schemas . set ( modelName , schema );
}
});
return schemas ;
}
private async generateRoutesWithValidation (
context : any ,
validationSchemas : Map < string , any >
) : Promise < void > {
// Implement custom route generation with validation
}
}
Example 3: Testing Route Generator
Here’s a minimal example used in tsoa’s own tests:
import { AbstractRouteGenerator } from '@tsoa/cli' ;
export class DummyRouteGenerator extends AbstractRouteGenerator < any > {
private static CALL_COUNT = 0 ;
GenerateCustomRoutes () : Promise < void > {
DummyRouteGenerator . CALL_COUNT += 1 ;
return Promise . resolve ( undefined );
}
public static getCallCount () : number {
return this . CALL_COUNT ;
}
}
Helper Methods
The AbstractRouteGenerator provides several utility methods:
protected pathTransformer ( path : string ): string {
// Convert Express-style paths (:id) to other formats
// Override this method to customize path transformation
return convertBracesPathParams ( path );
}
Import Path Resolution
protected getRelativeImportPath ( fileLocation : string ): string {
// Generates correct relative import paths
// Handles ESM/CommonJS extensions automatically
// Returns path like './controllers/userController.js'
}
Property and Parameter Builders
protected buildPropertySchema ( source : Tsoa . Property ): TsoaRoute . PropertySchema ;
protected buildParameterSchema ( source : Tsoa . Parameter ): TsoaRoute . ParameterSchema ;
protected buildProperty ( type : Tsoa . Type ): TsoaRoute . PropertySchema ;
These methods handle complex types including unions, intersections, arrays, nested objects, and reference types.
File Writing
The base class provides a helper for conditional file writing:
protected async shouldWriteFile ( fileName : string , content : string ): Promise < boolean > {
// Returns false if noWriteIfUnchanged is enabled and content matches existing file
// Helps avoid unnecessary rebuilds in watch mode
}
Best Practices
Start with the default generator
Examine DefaultRouteGenerator in the tsoa source code to understand the patterns and helper methods available.
Use type-safe access
Leverage TypeScript’s type system when accessing metadata to catch errors early.
Handle all parameter types
Ensure your generator properly maps path, query, body, header, and request parameters.
Support security
If your application uses authentication, implement proper security middleware integration.
Test thoroughly
Create integration tests that verify your generated routes work correctly with your target framework.
Reference Implementation
tsoa includes built-in route generators for Express, Koa, and Hapi. You can find them in:
packages/cli/src/routeGeneration/defaultRouteGenerator.ts
packages/cli/src/routeGeneration/routeGenerator.ts
packages/cli/src/routeGeneration/templates/*.hbs
Study these implementations for patterns on handling:
File uploads with Multer
Authentication middleware
IoC container integration
Validation error handling
Success status codes
Response header management
Middleware Learn about adding middleware to controllers and routes
Configuration Complete tsoa configuration reference