Overview
The generateRoutes function generates route registration code that wires up your tsoa controllers to your chosen web framework (Express, Koa, or Hapi). It creates a routes file that handles request validation, parameter extraction, and controller invocation.
Signature
async function generateRoutes<Config extends ExtendedRoutesConfig>(
routesConfig: Config,
compilerOptions?: ts.CompilerOptions,
ignorePaths?: string[],
metadata?: Tsoa.Metadata,
defaultNumberType?: BaseConfig['defaultNumberType']
): Promise<Tsoa.Metadata>
Parameters
routesConfig
ExtendedRoutesConfig
required
Configuration object for route generation. See RoutesConfig for details.Must include:
entryFile: The entry point to your API
routesDir: Where to write the generated routes file
middleware: The web framework (‘express’, ‘koa’, or ‘hapi’)
TypeScript compiler options to use during generation.If not provided, tsoa will use the compiler options from your tsconfig.json.
Array of path globs to ignore during TypeScript metadata scan.Example: ['**/node_modules/**', '**/dist/**']
Pre-computed metadata from a previous generation step (e.g., from generateSpec).Pass this to avoid re-scanning your controllers.
defaultNumberType
'double' | 'float' | 'integer' | 'long'
Default OpenAPI number type for TypeScript’s number type when no type annotation is present.Default: 'double'
Returns
The generated metadata object containing all controller and route information.This can be passed to generateSpec to avoid re-scanning your codebase.
Usage
Express Routes
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express',
basePath: '/api'
};
await generateRoutes(config);
// Generates: ./src/routes.ts
Koa Routes
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'koa',
basePath: '/api/v1'
};
await generateRoutes(config);
// Generates: ./src/routes.ts
Hapi Routes
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'hapi',
basePath: '/api'
};
await generateRoutes(config);
// Generates: ./src/routes.ts
Custom Routes Filename
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src/generated',
routesFileName: 'tsoa-routes.ts',
middleware: 'express'
};
await generateRoutes(config);
// Generates: ./src/generated/tsoa-routes.ts
With IoC Container (Dependency Injection)
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express',
iocModule: './src/ioc',
basePath: '/api'
};
await generateRoutes(config);
With Authentication Module
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express',
authenticationModule: './src/authentication',
basePath: '/api'
};
await generateRoutes(config);
ESM Support
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express',
esm: true, // Add .js extensions to imports
basePath: '/api'
};
await generateRoutes(config);
import { generateSpec, generateRoutes } from 'tsoa';
const specConfig = {
entryFile: './src/app.ts',
outputDirectory: './dist',
specVersion: 3
};
const routesConfig = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express'
};
// Generate spec first
const metadata = await generateSpec(specConfig);
// Reuse metadata for routes (avoids re-scanning)
await generateRoutes(routesConfig, undefined, undefined, metadata);
Avoid Writing If Unchanged
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express',
noWriteIfUnchanged: true // Don't write if file hasn't changed
};
await generateRoutes(config);
Full Configuration Example
import { generateRoutes } from 'tsoa';
const config = {
entryFile: './src/server.ts',
routesDir: './src/generated',
routesFileName: 'routes.ts',
middleware: 'express',
basePath: '/api/v2',
// IoC Container
iocModule: './src/inversify.config',
// Authentication
authenticationModule: './src/auth',
// ESM Support
esm: true,
// Optimization
noWriteIfUnchanged: true,
// Body coercion
bodyCoercion: true,
// Controller Discovery
controllerPathGlobs: [
'./src/controllers/**/*.ts',
'./src/modules/**/controllers/*.ts'
]
};
await generateRoutes(config);
Generated Routes File
Express Example
The generated routes file exports a RegisterRoutes function:
// Generated routes.ts
import { Controller, ValidateError, FieldErrors, TsoaResponse } from '@tsoa/runtime';
import { UserController } from './controllers/userController';
export function RegisterRoutes(app: Express.Application): void {
app.get('/api/users/:userId',
async function UserController_getUser(request: any, response: any, next: any) {
const args = {
userId: {"in":"path","name":"userId","required":true,"dataType":"string"},
};
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new UserController();
const promise = controller.getUser.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, undefined, next);
} catch (err) {
return next(err);
}
}
);
// ... more routes
}
Using Generated Routes
Express
import express from 'express';
import { RegisterRoutes } from './routes';
const app = express();
app.use(express.json());
// Register tsoa routes
RegisterRoutes(app);
// Error handler
app.use((err: any, req: any, res: any, next: any) => {
if (err instanceof ValidateError) {
return res.status(422).json({
message: "Validation Failed",
details: err.fields,
});
}
if (err) {
return res.status(500).json({
message: "Internal Server Error",
});
}
next();
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Koa
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import { RegisterRoutes } from './routes';
const app = new Koa();
app.use(bodyParser());
// Register tsoa routes
RegisterRoutes(app);
// Error handler
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
if (err instanceof ValidateError) {
ctx.status = 422;
ctx.body = {
message: "Validation Failed",
details: err.fields,
};
} else {
ctx.status = 500;
ctx.body = {
message: "Internal Server Error",
};
}
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Hapi
import Hapi from '@hapi/hapi';
import { RegisterRoutes } from './routes';
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
const init = async () => {
// Register tsoa routes
await RegisterRoutes(server);
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
IoC Container Integration
Use dependency injection with InversifyJS or other IoC containers:
inversify.config.ts
import { Container, inject, injectable } from 'inversify';
import { buildProviderModule } from 'inversify-binding-decorators';
import { UserService } from './services/userService';
import { UserController } from './controllers/userController';
const iocContainer = new Container();
// Bind services
iocContainer.bind<UserService>('UserService').to(UserService);
// Controllers are bound automatically by tsoa
export { iocContainer };
Controller with Dependency Injection
import { Controller, Get, Route } from 'tsoa';
import { inject, injectable } from 'inversify';
import { UserService } from '../services/userService';
@injectable()
@Route('users')
export class UserController extends Controller {
constructor(
@inject('UserService') private userService: UserService
) {
super();
}
@Get('{userId}')
public async getUser(userId: string): Promise<User> {
return await this.userService.findById(userId);
}
}
{
"routes": {
"iocModule": "./src/inversify.config"
}
}
Build Script Integration
package.json
{
"scripts": {
"build:routes": "ts-node src/scripts/generate-routes.ts",
"build:spec": "ts-node src/scripts/generate-spec.ts",
"build:all": "npm run build:routes && npm run build:spec",
"prebuild": "npm run build:all",
"dev": "npm run build:all && nodemon src/server.ts"
}
}
scripts/generate-routes.ts
import { generateRoutes } from 'tsoa';
import { routesConfig } from '../tsoa.config';
(async () => {
try {
console.log('Generating routes...');
await generateRoutes(routesConfig);
console.log('✓ Routes generated successfully');
} catch (error) {
console.error('Failed to generate routes:', error);
process.exit(1);
}
})();
Combined Script
import { generateSpec, generateRoutes } from 'tsoa';
import { specConfig, routesConfig } from '../tsoa.config';
(async () => {
try {
console.log('Generating OpenAPI spec and routes...');
// Generate spec and capture metadata
const metadata = await generateSpec(specConfig);
console.log('✓ Spec generated');
// Reuse metadata for routes
await generateRoutes(routesConfig, undefined, undefined, metadata);
console.log('✓ Routes generated');
console.log('Build complete!');
} catch (error) {
console.error('Build failed:', error);
process.exit(1);
}
})();
CLI Alternative
Instead of using the function directly, you can use the tsoa CLI:
# Generate routes using tsoa.json configuration
tsoa routes
# Generate routes with custom config file
tsoa routes -c ./custom-tsoa.json
# Generate both spec and routes
tsoa spec-and-routes
Common Issues
Missing Middleware Configuration
// Error: middleware is required
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express' // Must specify: 'express', 'koa', or 'hapi'
};
Controller Discovery
If controllers aren’t being discovered, specify controllerPathGlobs:
const config = {
entryFile: './src/app.ts',
routesDir: './src',
middleware: 'express',
controllerPathGlobs: [
'./src/controllers/**/*.ts',
'./src/modules/**/controllers/*.ts'
]
};
TypeScript Compilation
Make sure to compile the generated routes file:
Or import it directly in development:
import { RegisterRoutes } from './routes'; // TypeScript will compile on the fly
See Also