tsoa: TypeScript OpenAPI Framework
tsoa is a powerful TypeScript-first framework designed to streamline the creation of REST APIs by automatically generating OpenAPI (formerly Swagger) documentation and client SDKs directly from your existing TypeScript code. It employs decorators and standard TypeScript types to define API routes, request bodies, query parameters, and response structures. A critical step involves a code generation phase that produces both the OpenAPI specification and the concrete route registration logic for popular Node.js frameworks such as Express, Hapi, and Koa. The project exhibits an active development cycle, with consistent minor releases every few months, and has recently released a v7 alpha that introduces support for OpenAPI 3.1.0 and updated Node.js version requirements. Its key differentiator is the "code-first" approach, eliminating the need to manually maintain separate OpenAPI definitions by deriving them directly from the TypeScript source, ensuring strong type consistency between API implementation and documentation. The current stable version is v6.6.0.
Common errors
-
TS2307: Cannot find module '../build/routes' or its corresponding type declarations.
cause The `tsoa routes` command has not been executed, or the `routesDir` in `tsoa.json` is incorrect, preventing the generation of the `routes.ts` file.fixRun `npx tsoa routes` as part of your build process. Verify that `routesDir` in `tsoa.json` matches the intended output directory, and that the import path in your `app.ts` is correct relative to its compiled location. -
Error: No routes were found! Have you configured your tsoa.json correctly?
cause tsoa could not find any controller files matching the `controllerPathGlobs` specified in `tsoa.json`, or no classes within those files were decorated with `@Route()`.fixCheck the `controllerPathGlobs` array in your `tsoa.json` to ensure it correctly points to your controller files (e.g., `"src/controllers/**/*.ts"`). Also, confirm that your controller classes have the `@Route()` decorator. -
TypeError: Cannot read properties of undefined (reading 'body')
cause The Express (or other framework) application is not configured with middleware to parse incoming request bodies (e.g., JSON or URL-encoded data), or the wrong `@Body` decorator is used for the content type.fixFor JSON payloads, ensure `app.use(express.json())` is called early in your Express application setup. For URL-encoded data, use `app.use(express.urlencoded({ extended: true }))`. Use appropriate tsoa decorators like `@Body()`, `@BodyProp()`, or `@FormField()` for different request body types. -
MulterError: Unexpected field
cause A mismatch exists between the field name used in the client's form data upload and the field name expected by the `@UploadedFile()` or `@UploadedFiles()` decorator in the controller, or Multer isn't correctly initialized/passed.fixEnsure the string argument passed to `@UploadedFile('fieldName')` or `@UploadedFiles('fieldName')` exactly matches the `name` attribute of the file input in your HTML form or the key in your `FormData` object. If using custom Multer, ensure it's configured correctly. -
Error: target is a string value; tsconfig JSON must be parsed with parseJsonSourceFileConfigFileContent or getParsedCommandLineOfConfigFile before passing to createProgram
cause This error can occur in tsoa v6.5.0+ when `compilerOptions` are explicitly passed in `tsoa.json` directly as a string or in an incorrect format that TypeScript's `createProgram` cannot parse. This often happens with shared `tsconfig` files or non-standard configurations.fixRemove the explicit `compilerOptions` field from `tsoa.json` and let tsoa infer them from your main `tsconfig.json`. If custom options are strictly needed, ensure they are provided in a correctly structured object that `ts.createProgram` expects, or consider passing a `compilerOptions` object programmatically to `generateSpec` and `generateRoutes` functions if using the API directly.
Warnings
- breaking tsoa v7.0.0-alpha.0 (and newer) has updated its officially supported Node.js versions. While v6 requires Node.js >= 18.0.0, v7 specifically tests against and officially supports Node.js versions 22, 24, and 25. Using older Node.js versions with v7 might lead to unexpected behavior or failures.
- breaking Since v7.0.0-alpha.0, OpenAPI 3.1.0 is the default output format for the generated specification. If your tooling (e.g., client SDK generators, API gateways) does not yet support OpenAPI 3.1.0, you will need to explicitly configure tsoa to output an older version (e.g., 3.0.0) in your `tsoa.json` file.
- gotcha tsoa relies heavily on a code generation step (`tsoa spec` and `tsoa routes`) which *must* be executed before your application is compiled and run. Forgetting this step will result in missing route definitions and OpenAPI specifications, leading to runtime errors like 'Cannot find module ../build/routes'.
- gotcha Handling file uploads with `@UploadedFile()` and `@UploadedFiles()` decorators often requires `multer`. Earlier versions (pre-v6.4.0) had less flexibility, sometimes leading to conflicts or issues with custom Multer configurations. While `tsoa.json` can configure `multerOptions`, for advanced use cases (e.g., custom storage engines like `multer-s3`), directly integrating Multer in the controller might be necessary.
- gotcha tsoa requires `experimentalDecorators` and `emitDecoratorMetadata` to be enabled in your `tsconfig.json`. Without these TypeScript compiler options, decorators like `@Route`, `@Get`, and `@Body` will not function correctly, leading to compilation errors or unexpected runtime behavior where routes are not registered.
Install
-
npm install tsoa -
yarn add tsoa -
pnpm add tsoa
Imports
- Controller
import { Controller } from '@tsoa/runtime'import { Controller, Route, Get, Post, Body, Path, Query } from 'tsoa' - RegisterRoutes
import { RegisterRoutes } from 'tsoa'import { RegisterRoutes } from '../build/routes' - tsoa CLI
tsoa generate
npx tsoa spec && npx tsoa routes
Quickstart
// tsoa.json (in project root)
// {
// "entryFile": "src/app.ts",
// "controllers": ["src/controllers/**/*.ts"],
// "spec": {
// "outputDirectory": "build",
// "specVersion": 3,
// "yaml": false
// },
// "routes": {
// "routesDir": "build"
// }
// }
// src/controllers/usersController.ts
import { Controller, Route, Get, Path, Post, Body, SuccessResponse } from 'tsoa';
interface User {
id: number;
name: string;
}
interface CreateUserRequest {
name: string;
}
@Route('users')
export class UsersController extends Controller {
private users: User[] = [{ id: 1, name: 'Alice' }];
@Get('{userId}')
public async getUser(@Path() userId: number): Promise<User | undefined> {
return this.users.find(u => u.id === userId);
}
@SuccessResponse(201, 'Created') // Set HTTP status code for success
@Post()
public async createUser(@Body() requestBody: CreateUserRequest): Promise<User> {
const newUser: User = {
id: this.users.length + 1,
name: requestBody.name,
};
this.users.push(newUser);
return newUser;
}
}
// src/app.ts (your Express server entry file)
import express from 'express';
import { RegisterRoutes } from '../build/routes'; // Path relative to app.ts's compiled output
import * as swaggerUi from 'swagger-ui-express';
import * as path from 'path';
const app = express();
app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
RegisterRoutes(app); // Register the tsoa-generated routes
// Serve OpenAPI UI
try {
// Ensure 'swagger.json' is generated by `tsoa spec` in your build directory
const swaggerDocument = require(path.resolve(__dirname, '../build/swagger.json'));
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
} catch (error) {
console.error('Failed to load swagger.json. Did you run `tsoa spec`?', error);
}
// Generic error handler middleware
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err);
const status = err.status || 500;
const message = err.message || 'An unexpected error occurred.';
res.status(status).json({ message });
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
console.log(`API docs available at http://localhost:${port}/docs`);
});
// To run this application:
// 1. Install dependencies: `npm install express tsoa swagger-ui-express @types/express @types/swagger-ui-express typescript ts-node @tsoa/runtime`
// 2. Configure `tsconfig.json` with: `"experimentalDecorators": true`, `"emitDecoratorMetadata": true` (recommended).
// 3. Add scripts to `package.json`: `"tsoa:gen": "tsoa spec && tsoa routes", "build": "npm run tsoa:gen && tsc", "start": "npm run build && node build/app.js"`
// 4. Run `npm start` to build and launch the server.