Overview
The RoutesConfig interface defines configuration options for generating route registration code for your chosen web framework. The generated routes file handles request validation, parameter extraction, and controller method invocation.
Required Properties
routesDir
Directory where the generated routes file will be written.
{
"routes": {
"routesDir": "src"
}
}
Generates: src/routes.ts (or custom filename if routesFileName is set)
middleware
middleware
'express' | 'koa' | 'hapi'
Web framework to generate routes for.If not specified, defaults to 'express'.
{
"routes": {
"routesDir": "src",
"middleware": "express"
}
}
Optional Properties
routesFileName
routesFileName
string
default:"routes.ts"
Filename for the generated routes file.
{
"routes": {
"routesDir": "src/generated",
"routesFileName": "tsoa-routes.ts"
}
}
Generates: src/generated/tsoa-routes.ts
basePath
Base path prefix for all routes.Example: "/api/v1" makes routes accessible at https://example.com/api/v1/*
{
"routes": {
"routesDir": "src",
"middleware": "express",
"basePath": "/api/v1"
}
}
Example:
@Route('users')
export class UserController {
@Get('{userId}')
public async getUser(@Path() userId: string): Promise<User> {
// Without basePath: GET /users/{userId}
// With basePath "/api/v1": GET /api/v1/users/{userId}
return await userService.findById(userId);
}
}
noWriteIfUnchanged
Don’t write the routes file if the content hasn’t changed.Useful for optimizing watch processes and avoiding unnecessary rebuilds.
{
"routes": {
"routesDir": "src",
"middleware": "express",
"noWriteIfUnchanged": true
}
}
Framework-Specific Options
middlewareTemplate
Path to a custom middleware template file.Allows complete customization of the generated routes file.
{
"routes": {
"routesDir": "src",
"middlewareTemplate": "./templates/custom-routes.hbs"
}
}
Dependency Injection
iocModule
Path to IoC (Inversion of Control) container module.Used for dependency injection with InversifyJS or similar libraries.
{
"routes": {
"routesDir": "src",
"middleware": "express",
"iocModule": "./src/inversify.config"
}
}
Example IoC Module:
// src/inversify.config.ts
import { Container } from 'inversify';
import { UserService } from './services/userService';
import { UserController } from './controllers/userController';
const iocContainer = new Container();
iocContainer.bind<UserService>('UserService').to(UserService);
iocContainer.bind<UserController>(UserController).toSelf();
export { iocContainer };
Controller with DI:
import { injectable, inject } from 'inversify';
import { Controller, Get, Route } from 'tsoa';
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(@Path() userId: string): Promise<User> {
return await this.userService.findById(userId);
}
}
Authentication
authenticationModule
Path to authentication module.Module should export an authentication function for your framework.
{
"routes": {
"routesDir": "src",
"middleware": "express",
"authenticationModule": "./src/authentication"
}
}
Express Authentication Module:
// src/authentication.ts
import { Request } from 'express';
import * as jwt from 'jsonwebtoken';
export async function expressAuthentication(
request: Request,
securityName: string,
scopes?: string[]
): Promise<any> {
if (securityName === 'bearer') {
const token = request.header('Authorization')?.replace('Bearer ', '');
if (!token) {
throw new Error('No token provided');
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
// Check scopes if provided
if (scopes && scopes.length > 0) {
const userScopes = (decoded as any).scopes || [];
const hasRequiredScopes = scopes.every(scope =>
userScopes.includes(scope)
);
if (!hasRequiredScopes) {
throw new Error('Insufficient permissions');
}
}
return decoded;
} catch (err) {
throw new Error('Invalid token');
}
}
throw new Error('Unknown security scheme');
}
Koa Authentication Module:
// src/authentication.ts
import { Context } from 'koa';
import * as jwt from 'jsonwebtoken';
export async function koaAuthentication(
context: Context,
securityName: string,
scopes?: string[]
): Promise<any> {
if (securityName === 'bearer') {
const token = context.header.authorization?.replace('Bearer ', '');
if (!token) {
throw new Error('No token provided');
}
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
return decoded;
}
throw new Error('Unknown security scheme');
}
Hapi Authentication Module:
// src/authentication.ts
import { Request } from '@hapi/hapi';
import * as jwt from 'jsonwebtoken';
export async function hapiAuthentication(
request: Request,
securityName: string,
scopes?: string[]
): Promise<any> {
if (securityName === 'bearer') {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new Error('No token provided');
}
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
return decoded;
}
throw new Error('Unknown security scheme');
}
Module System Options
esm
Add .js extensions to imports for ES module support.Required when using "type": "module" in package.json.
{
"routes": {
"routesDir": "src",
"middleware": "express",
"esm": true
}
}
Generated imports with ESM:
import { UserController } from './controllers/userController.js';
import { ProductController } from './controllers/productController.js';
rewriteRelativeImportExtensions
rewriteRelativeImportExtensions
Keep .ts extensions in imports for TypeScript 5.7+ rewriteRelativeImportExtensions feature.
{
"routes": {
"routesDir": "src",
"middleware": "express",
"rewriteRelativeImportExtensions": true
}
}
Generated imports:
import { UserController } from './controllers/userController.ts';
Validation Options
bodyCoercion
Implicitly coerce body parameters to their declared types.When true, strings like "123" are converted to numbers if the parameter type is number.
{
"routes": {
"routesDir": "src",
"middleware": "express",
"bodyCoercion": true
}
}
Example:
interface CreateUserRequest {
age: number;
active: boolean;
}
@Post('users')
public async createUser(@Body() body: CreateUserRequest): Promise<User> {
// With bodyCoercion: true
// Request: { "age": "25", "active": "true" }
// Body received: { age: 25, active: true }
// With bodyCoercion: false
// Request: { "age": "25", "active": "true" }
// Validation error: age must be a number
return await userService.create(body);
}
File Upload Configuration
multerOpts (Deprecated)
Multer options for file upload handling.Deprecated: Pass multer options to RegisterRoutes instead.
Modern approach (recommended):
import express from 'express';
import multer from 'multer';
import { RegisterRoutes } from './routes';
const app = express();
const multerOpts = {
dest: '/tmp/uploads',
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5
},
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only images allowed'));
}
}
};
RegisterRoutes(app, { multerOpts });
Complete Examples
Minimal Configuration
{
"routes": {
"routesDir": "src",
"middleware": "express"
}
}
Development Configuration
{
"routes": {
"routesDir": "src",
"middleware": "express",
"basePath": "/api",
"noWriteIfUnchanged": true
}
}
Production Configuration
{
"routes": {
"routesDir": "src/generated",
"routesFileName": "routes.ts",
"middleware": "express",
"basePath": "/api/v2",
"authenticationModule": "./src/authentication",
"iocModule": "./src/inversify.config",
"noWriteIfUnchanged": true,
"bodyCoercion": true
}
}
ESM Project
{
"routes": {
"routesDir": "src",
"middleware": "express",
"esm": true,
"basePath": "/api"
}
}
Koa with Authentication
{
"routes": {
"routesDir": "src",
"middleware": "koa",
"basePath": "/api/v1",
"authenticationModule": "./src/authentication"
}
}
Hapi with IoC
{
"routes": {
"routesDir": "src",
"middleware": "hapi",
"iocModule": "./src/inversify.config",
"basePath": "/api"
}
}
Framework Usage
Express
import express from 'express';
import { RegisterRoutes } from './routes';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
RegisterRoutes(app);
app.listen(3000);
Koa
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import { RegisterRoutes } from './routes';
const app = new Koa();
app.use(bodyParser());
RegisterRoutes(app);
app.listen(3000);
Hapi
import Hapi from '@hapi/hapi';
import { RegisterRoutes } from './routes';
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
const init = async () => {
await RegisterRoutes(server);
await server.start();
};
init();
See Also