Apply Express, Koa, or Hapi middleware to your tsoa controllers and routes
tsoa provides a @Middlewares decorator that allows you to attach framework-specific middleware to controllers or individual routes. This enables you to leverage existing middleware ecosystems while maintaining type safety.
Middleware in tsoa is framework-agnostic at the decorator level but framework-specific at runtime. The @Middlewares decorator accepts middleware functions compatible with your chosen framework (Express, Koa, or Hapi).
Middleware is executed in the order specified, from outermost decorator to innermost, with controller-level middleware running before method-level middleware.
import { Controller, Get, Post, Route, Middlewares } from 'tsoa';import { authenticate } from './middleware/auth';@Route('admin')@Middlewares(authenticate)export class AdminController extends Controller { @Get('users') public async getUsers(): Promise<User[]> { // This route is protected by authenticate middleware return await userService.getAll(); } @Post('users') public async createUser(requestBody: CreateUserRequest): Promise<User> { // This route is also protected by authenticate middleware return await userService.create(requestBody); }}
import { Controller, Get, Route, Middlewares } from 'tsoa';import type { Request, Response, NextFunction } from 'express';import jwt from 'jsonwebtoken';function authenticate(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ message: 'No token provided' }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET!); (req as any).user = decoded; next(); } catch (error) { return res.status(401).json({ message: 'Invalid token' }); }}@Route('protected')@Middlewares(authenticate)export class ProtectedController extends Controller { @Get('profile') public async getProfile(): Promise<Profile> { // Access user from request const userId = (this.getRequest() as any).user.id; return await profileService.getById(userId); }}
For OAuth2/JWT authentication with OpenAPI integration, prefer using tsoa’s built-in @Security decorator instead of custom middleware. See the Authentication guide for details.
import { Controller, Post, Route, Middlewares } from 'tsoa';import type { Request, Response, NextFunction } from 'express';import { z } from 'zod';function validateSchema<T>(schema: z.ZodSchema<T>) { return (req: Request, res: Response, next: NextFunction) => { try { schema.parse(req.body); next(); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: 'Validation failed', errors: error.errors }); } next(error); } };}const createUserSchema = z.object({ email: z.string().email(), password: z.string().min(8), name: z.string().min(2)});@Route('users')export class UserController extends Controller { @Post() @Middlewares(validateSchema(createUserSchema)) public async createUser(requestBody: CreateUserRequest): Promise<User> { // Body is already validated by middleware return await userService.create(requestBody); }}
tsoa provides built-in runtime validation based on your TypeScript types. Custom validation middleware is typically only needed for complex validation rules not expressible in TypeScript.
The @Middlewares decorator is metadata-only. Middleware execution is handled by the generated routes file, which means middleware must be available at runtime when routes are registered.
Middleware limitations:
Middleware functions must be statically analyzable (no dynamic middleware generation at route registration time)
Type information for modified request/response objects is not automatically propagated
Framework switching requires updating all middleware implementations