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.
This guide walks you through integrating tsoa with a Koa.js application, from initial setup to running your API server.
Installation
Install Dependencies
Install tsoa along with Koa and its dependencies:npm install tsoa koa @koa/router koa-bodyparser
npm install --save-dev @types/koa @types/koa__router @types/node typescript
Configure tsoa
Create a tsoa.json configuration file in your project root:{
"entryFile": "src/app.ts",
"noImplicitAdditionalProperties": "throw-on-extras",
"spec": {
"outputDirectory": "build",
"specVersion": 3
},
"routes": {
"routesDir": "src",
"middleware": "koa"
}
}
The middleware field must be set to "koa" for Koa.js projects.
Create a Controller
Create your first controller with tsoa decorators:src/controllers/userController.ts
import { Controller, Get, Post, Route, Body, Path, SuccessResponse } from 'tsoa';
interface User {
id: number;
name: string;
email: string;
}
interface CreateUserRequest {
name: string;
email: string;
}
@Route('users')
export class UserController extends Controller {
/**
* Retrieves a user by ID
* @param userId The user's identifier
*/
@Get('{userId}')
public async getUser(@Path() userId: number): Promise<User> {
return {
id: userId,
name: 'John Doe',
email: 'john@example.com'
};
}
/**
* Creates a new user
*/
@Post()
@SuccessResponse('201', 'Created')
public async createUser(@Body() requestBody: CreateUserRequest): Promise<User> {
this.setStatus(201);
return {
id: Math.floor(Math.random() * 10000),
...requestBody
};
}
}
Set Up Koa Server
Create your Koa application and register tsoa routes:import Koa from 'koa';
import Router from '@koa/router';
import bodyParser from 'koa-bodyparser';
import { RegisterRoutes } from './routes';
const app = new Koa();
const router = new Router();
// Middleware
app.use(bodyParser());
// Register tsoa routes
RegisterRoutes(router);
// Error handling - must come before routes
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
ctx.status = err.status || 500;
ctx.body = {
message: err.message || 'An error occurred during the request.',
status: ctx.status,
fields: err.fields
};
}
});
// Apply routes
app.use(router.routes());
app.use(router.allowedMethods());
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Generate Routes and Spec
Add scripts to your package.json:{
"scripts": {
"build": "tsoa spec-and-routes && tsc",
"start": "node build/app.js",
"dev": "tsoa spec-and-routes && ts-node src/app.ts"
}
}
Generate routes and OpenAPI spec:This creates:
src/routes.ts - Generated route handlers
build/swagger.json - OpenAPI specification
Start the Server
Your API is now running at http://localhost:3000!Test it:curl http://localhost:3000/users/1
Koa Context Access
Access the Koa context object in your controllers:
import { Controller, Get, Request } from 'tsoa';
import { Context } from 'koa';
@Route('example')
export class ExampleController extends Controller {
@Get('with-context')
public async withContext(
@Request() ctx: Context
): Promise<any> {
// Access request headers
const userAgent = ctx.headers['user-agent'];
// Access custom state
const userId = ctx.state.userId;
// Set response headers
ctx.set('X-Custom-Header', 'value');
return { userAgent, userId };
}
}
Middleware Integration
Apply Koa middleware to specific routes or controllers:
import { Controller, Get, Middlewares } from 'tsoa';
import { Context, Next, Middleware } from 'koa';
const authMiddleware: Middleware = async (ctx: Context, next: Next) => {
if (ctx.headers.authorization) {
// Set user info in context state
ctx.state.user = await verifyToken(ctx.headers.authorization);
await next();
} else {
ctx.throw(401, 'Unauthorized');
}
};
const loggingMiddleware: Middleware = async (ctx: Context, next: Next) => {
console.log(`${ctx.method} ${ctx.path}`);
await next();
};
@Route('protected')
@Middlewares(authMiddleware)
export class ProtectedController extends Controller {
@Get('data')
@Middlewares(loggingMiddleware)
public async getData(): Promise<any> {
return { data: 'sensitive information' };
}
}
Error Handling
Koa uses a different error handling pattern than Express:
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
ctx.status = err.status || 500;
ctx.body = {
message: err.message,
status: ctx.status
};
}
});
Using ctx.throw
Koa provides a convenient ctx.throw() method:
import { Controller, Get, Path, Request } from 'tsoa';
import { Context } from 'koa';
@Route('items')
export class ItemController extends Controller {
@Get('{id}')
public async getItem(
@Path() id: number,
@Request() ctx: Context
): Promise<Item> {
const item = await findItem(id);
if (!item) {
ctx.throw(404, 'Item not found');
}
return item;
}
}
State Management
Share data between middleware using ctx.state:
// Middleware to set state
const userMiddleware: Middleware = async (ctx, next) => {
const token = ctx.headers.authorization;
ctx.state.user = await getUserFromToken(token);
await next();
};
// Controller accessing state
@Route('profile')
@Middlewares(userMiddleware)
export class ProfileController extends Controller {
@Get()
public async getProfile(@Request() ctx: Context): Promise<User> {
return ctx.state.user;
}
}
Base Path Configuration
Configure a base path for all routes:
{
"routes": {
"routesDir": "src",
"middleware": "koa",
"basePath": "/api/v1"
}
}
All routes will be prefixed with /api/v1.
Router Prefix
Alternatively, use Koa Router’s prefix option:
const router = new Router({
prefix: '/api/v1'
});
RegisterRoutes(router);
Query Parameters
import { Controller, Get, Query } from 'tsoa';
@Route('search')
export class SearchController extends Controller {
@Get()
public async search(
@Query() q: string,
@Query() limit?: number,
@Query() offset?: number
): Promise<SearchResult[]> {
return performSearch(q, limit || 10, offset || 0);
}
}
import { Controller, Get, Header, Request } from 'tsoa';
import { Context } from 'koa';
@Route('api')
export class ApiController extends Controller {
@Get('version')
public async getVersion(
@Header('x-api-version') apiVersion?: string,
@Request() ctx: Context
): Promise<any> {
// Set response header
ctx.set('X-Response-Version', '1.0');
return { requestedVersion: apiVersion };
}
}
Comparison with Express
import express from 'express';
import { RegisterRoutes } from './routes';
const app = express();
RegisterRoutes(app);
app.use((err, req, res, next) => {
res.status(err.status || 500).json(err);
});
import Koa from 'koa';
import Router from '@koa/router';
import { RegisterRoutes } from './routes';
const app = new Koa();
const router = new Router();
RegisterRoutes(router);
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
ctx.status = err.status || 500;
ctx.body = err;
}
});
app.use(router.routes());
Next Steps
File Uploads
Learn how to handle file uploads with Koa
Authentication
Secure your API with authentication
Validation
Implement request validation
Dependency Injection
Use IoC containers with your controllers