Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lukeautry/tsoa/llms.txt

Use this file to discover all available pages before exploring further.

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’)
compilerOptions
ts.CompilerOptions
TypeScript compiler options to use during generation.If not provided, tsoa will use the compiler options from your tsconfig.json.
ignorePaths
string[]
Array of path globs to ignore during TypeScript metadata scan.Example: ['**/node_modules/**', '**/dist/**']
metadata
Tsoa.Metadata
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

metadata
Tsoa.Metadata
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);

Reusing Metadata from Spec Generation

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);
  }
}

Configure in tsoa.json

{
  "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:
tsc
Or import it directly in development:
import { RegisterRoutes } from './routes'; // TypeScript will compile on the fly

See Also