Overview
Response decorators document and control HTTP responses, including status codes, response types, headers, and content types. They generate accurate OpenAPI documentation and enable type-safe response handling.
@SuccessResponse
Defines the success response status code and description for an endpoint.
function SuccessResponse<HeaderType = object>(
name: string | number,
description?: string,
produces?: string | string[]
): MethodDecorator
The HTTP status code (e.g., 200, 201, 204).
Description of the success response.
Content type(s) for this response.
Usage
import { Post, Body, Route, SuccessResponse } from 'tsoa';
@Route('users')
export class UserController {
@SuccessResponse(201, 'Created')
@Post()
public async createUser(
@Body() body: CreateUserRequest
): Promise<User> {
const user = await userService.create(body);
this.setStatus(201);
return user;
}
@SuccessResponse(204, 'No Content')
@Delete('{userId}')
public async deleteUser(@Path() userId: string): Promise<void> {
await userService.delete(userId);
this.setStatus(204);
}
@SuccessResponse(418, "I'm a teapot")
@Get('teapot')
public async teapot(): Promise<void> {
this.setStatus(418);
}
}
@Response
Defines error or alternative response types with status codes.
function Response<ExampleType, HeaderType = object>(
name: HttpStatusCodeLiteral | HttpStatusCodeStringLiteral | OtherValidOpenApiHttpStatusCode,
description?: string,
example?: ExampleType,
produces?: string | string[]
): MethodDecorator & ClassDecorator
The HTTP status code for this response (e.g., 400, 404, 500).
Description of this response type.
Example response body for this status code.
Content type(s) for this response.
Method-Level Error Responses
import { Get, Path, Route, Response } from 'tsoa';
interface ErrorResponse {
message: string;
code: string;
}
@Route('users')
export class UserController {
@Response<ErrorResponse>(404, 'User not found', {
message: 'User with specified ID does not exist',
code: 'USER_NOT_FOUND'
})
@Response<ErrorResponse>(400, 'Invalid request', {
message: 'User ID must be a valid UUID',
code: 'INVALID_USER_ID'
})
@Get('{userId}')
public async getUser(@Path() userId: string): Promise<User> {
const user = await userService.findById(userId);
if (!user) {
throw new NotFoundError('User not found');
}
return user;
}
}
Controller-Level Error Responses
Apply the same error responses to all methods in a controller:
import { Get, Post, Delete, Route, Response } from 'tsoa';
interface ErrorResponse {
message: string;
code: string;
timestamp: Date;
}
@Response<ErrorResponse>(401, 'Unauthorized', {
message: 'Authentication required',
code: 'UNAUTHORIZED',
timestamp: new Date()
})
@Response<ErrorResponse>(403, 'Forbidden', {
message: 'Insufficient permissions',
code: 'FORBIDDEN',
timestamp: new Date()
})
@Response<ErrorResponse>(500, 'Internal Server Error', {
message: 'An unexpected error occurred',
code: 'INTERNAL_ERROR',
timestamp: new Date()
})
@Route('admin')
export class AdminController {
// All methods inherit the controller-level @Response decorators
@Get('users')
public async listUsers(): Promise<User[]> {
return await userService.findAll();
}
@Delete('users/{userId}')
public async deleteUser(@Path() userId: string): Promise<void> {
await userService.delete(userId);
}
}
Multiple Status Codes
@Response<ErrorResponse>(400, 'Bad Request')
@Response<ErrorResponse>(404, 'Not Found')
@Response<ErrorResponse>(409, 'Conflict')
@Response<ErrorResponse>(451, 'Unavailable For Legal Reasons')
@Post('posts')
public async createPost(
@Body() body: CreatePostRequest
): Promise<Post> {
return await postService.create(body);
}
@Res - Type-Safe Response Handler
Injects a type-safe response function for sending alternative responses.
function Res(): ParameterDecorator
Use with TsoaResponse<Status, Data, Headers> type for type checking:
import { Get, Res, Route, TsoaResponse } from 'tsoa';
interface ErrorResponse {
errorMessage: string;
errorCode: number;
}
@Route('api')
export class ApiController {
/**
* @param errorResponse Error response handler
*/
@Response<ErrorResponse>(400, 'Bad Request')
@Get('data/{id}')
public async getData(
@Path() id: string,
@Res() errorResponse: TsoaResponse<400, ErrorResponse, { 'X-Error-ID': string }>
): Promise<DataResponse> {
if (!isValidId(id)) {
return errorResponse(400, {
errorMessage: 'Invalid ID format',
errorCode: 40001
}, {
'X-Error-ID': generateErrorId()
});
}
return await dataService.get(id);
}
@Response<ErrorResponse>(400, 'Bad Request')
@Response<ErrorResponse>(404, 'Not Found')
@Get('users/{userId}')
public async getUser(
@Path() userId: string,
@Res() notFoundResponse: TsoaResponse<404, ErrorResponse>,
@Res() badRequestResponse: TsoaResponse<400, ErrorResponse>
): Promise<User> {
if (!isValidUserId(userId)) {
return badRequestResponse(400, {
errorMessage: 'Invalid user ID',
errorCode: 40002
});
}
const user = await userService.findById(userId);
if (!user) {
return notFoundResponse(404, {
errorMessage: 'User not found',
errorCode: 40401
});
}
return user;
}
}
@Produces
Overrides the default content type for responses.
function Produces(value: string): MethodDecorator & ClassDecorator
The content type (MIME type) for the response.
Method-Level Content Type
import { Get, Route, Produces } from 'tsoa';
@Route('files')
export class FileController {
@Produces('text/plain')
@Get('readme')
public async getReadme(): Promise<string> {
return await fileService.readReadme();
}
@Produces('application/pdf')
@Get('report/{reportId}')
public async getReport(@Path() reportId: string): Promise<Buffer> {
return await reportService.generatePdf(reportId);
}
@Produces('image/png')
@Get('qrcode')
public async generateQRCode(@Query() data: string): Promise<Buffer> {
return await qrService.generate(data);
}
@Produces('text/csv')
@Get('export')
public async exportData(): Promise<string> {
return await dataService.exportToCsv();
}
}
Controller-Level Content Type
@Produces('application/json')
@Route('api')
export class ApiController {
// All methods return JSON by default
@Get('data')
public async getData(): Promise<any> {
return { data: 'value' };
}
// Override for specific method
@Produces('text/plain')
@Get('health')
public async health(): Promise<string> {
return 'OK';
}
}
Multiple Content Types
@Produces('application/json')
@Produces('application/xml')
@Get('data')
public async getData(
@Header('Accept') accept: string
): Promise<Data> {
const data = await dataService.get();
if (accept.includes('xml')) {
this.setHeader('Content-Type', 'application/xml');
}
return data;
}
@Consumes
Specifies the content type(s) accepted by a request body.
function Consumes(value: string): MethodDecorator
The content type (MIME type) accepted for the request body.
import { Post, Body, Route, Consumes } from 'tsoa';
@Route('api')
export class ApiController {
@Consumes('application/json')
@Post('data')
public async postJson(@Body() data: any): Promise<void> {
await dataService.save(data);
}
@Consumes('application/xml')
@Post('xml')
public async postXml(@Body() xml: string): Promise<void> {
await dataService.saveXml(xml);
}
@Consumes('multipart/form-data')
@Post('upload')
public async upload(
@UploadedFile() file: File,
@FormField() title: string
): Promise<void> {
await fileService.upload(file, title);
}
}
Use the setHeader method from the Controller base class:
import { Controller, Get, Route } from 'tsoa';
@Route('api')
export class ApiController extends Controller {
@Get('data')
public async getData(): Promise<Data> {
// Set a single header
this.setHeader('X-Custom-Header', 'value');
// Set cache headers
this.setHeader('Cache-Control', 'public, max-age=3600');
this.setHeader('ETag', generateETag());
// Set multiple Set-Cookie headers
this.setHeader('Set-Cookie', [
'token=abc123; HttpOnly; Secure',
'session=xyz789; HttpOnly; Secure'
]);
return await dataService.get();
}
}
Setting Status Codes
Use the setStatus method from the Controller base class:
import { Controller, Post, Delete, Route } from 'tsoa';
@Route('resources')
export class ResourceController extends Controller {
@Post()
public async create(@Body() data: CreateRequest): Promise<Resource> {
const resource = await resourceService.create(data);
this.setStatus(201); // Created
return resource;
}
@Delete('{id}')
public async delete(@Path() id: string): Promise<void> {
await resourceService.delete(id);
this.setStatus(204); // No Content
}
}
Common HTTP Status Codes
Success Codes
200 - OK: Standard success response
201 - Created: Resource successfully created
202 - Accepted: Request accepted for processing
204 - No Content: Success with no response body
Client Error Codes
400 - Bad Request: Invalid request syntax or parameters
401 - Unauthorized: Authentication required
403 - Forbidden: Authenticated but not authorized
404 - Not Found: Resource does not exist
409 - Conflict: Request conflicts with current state
422 - Unprocessable Entity: Validation failed
429 - Too Many Requests: Rate limit exceeded
Server Error Codes
500 - Internal Server Error: Unexpected server error
502 - Bad Gateway: Invalid response from upstream server
503 - Service Unavailable: Server temporarily unavailable
504 - Gateway Timeout: Upstream server timeout
See Also