Decorators
This section covers Inversify HTTP decorators used to provide related metadata.
Many decorators in this framework accept ServiceIdentifier types for dependency injection. A ServiceIdentifier<T> can be:
- A class constructor (e.g.,
MyMiddleware) - A string token (e.g.,
'my-middleware') - A symbol (e.g.,
Symbol.for('my-middleware')) - An abstract class reference
This allows for flexible dependency resolution through the InversifyJS container.
HTTP Request lifecycle
Inversify HTTP decorators can be used to manage the lifecycle of HTTP requests, including middleware execution, request handling, and response generation. This allows for more modular and maintainable code.
ApplyMiddleware
Attaches middleware to a controller class (applies to all its routes) or to a specific route method.
ApplyMiddleware(
...middlewareList: (ServiceIdentifier<Middleware> | ApplyMiddlewareOptions)[],
): ClassDecorator & MethodDecorator
Parameters
middlewareList: One or more middleware service identifiers or options objects.ServiceIdentifier<Middleware>: A middleware class, string token, symbol, or abstract class that can be resolved by the DI container.ApplyMiddlewareOptions: Configuration object with middleware and phase settings.
Notes
- Scope: Place the decorator on the controller class to affect all routes, or on a method to affect only that route.
- Phase: When passing middleware service identifiers directly (not the options object), they run in the
PreHandlerphase by default. - Order: Within the same phase, middleware execute in the order they are provided.
- See also: Middleware fundamentals for adapter-specific request/response types and tips.
Example: apply middleware to a controller (all routes)
@ApplyMiddleware(MyMiddleware)
@Controller('/ping')
class PingController {
@Get()
public async get(): Promise<string> {
return 'pong';
}
}
Example: apply middleware to a single route
@Controller('/ping')
class PingController {
@ApplyMiddleware(MyMiddleware)
@Get()
public async get(): Promise<string> {
return 'pong';
}
}
Next
Injects the framework-specific next function into your controller method. Call it to continue a pipeline. For example, a middleware can run first (e.g., to set headers), then your controller can return the response.
Next(): ParameterDecorator
Example: calling next per adapter
- Express 4
- Express 5
- Fastify
- Hono
- uWebSockets.js
@Controller('/next')
export class NextExpress4Controller {
@UseInterceptor(Express4NextInterceptor)
@Get()
public getNext(@Next() next: NextFunction): void {
next();
}
}
@Controller('/next')
@UseInterceptor(ExpressNextInterceptor)
export class NextExpressController {
@Get()
public getNext(@Next() next: NextFunction): void {
next();
}
}
@Controller('/next')
@UseInterceptor(FastifyNextInterceptor)
export class NextFastifyController {
@Get()
public getNext(@Next() next: () => void): void {
next();
}
}
@Controller('/next')
export class NextHonoController {
@UseInterceptor(HonoNextInterceptor)
@Get()
public async getNext(@Next() next: HonoNext): Promise<void> {
await next();
}
}
@Controller('/next')
export class NextUwebsocketsController {
@UseInterceptor(UwebsocketsNextInterceptor)
@Get()
public getNext(@Next() next: () => void): void {
next();
}
}
UseErrorFilter
Attaches one or more error filters to a controller class or method. Error filters handle exceptions that occur during request processing.
UseErrorFilter(...errorFilterList: Newable<ErrorFilter>[]): ClassDecorator & MethodDecorator
Parameters
errorFilterList: One or more error filter classes implementingErrorFilter.
Notes
- Scope: Place on the controller class to affect all routes, or on a method to affect only that route.
- Order: Error filters are processed in the order they are provided.
- Exception handling: Error filters can catch, transform, or handle exceptions thrown during request processing.
UseGuard
Attaches one or more guards to a controller class or method. All guards must allow for the request to continue.
UseGuard(...guardList: ServiceIdentifier<Guard>[]): ClassDecorator & MethodDecorator
Parameters
guardList: One or more guard service identifiers that can be resolved by the DI container.
UseInterceptor
Attaches one or more interceptors to a controller class or method. Interceptors can modify requests before they reach the handler and responses before they are sent.
UseInterceptor(...interceptorList: ServiceIdentifier<Interceptor>[]): ClassDecorator & MethodDecorator
Parameters
interceptorList: One or more interceptor service identifiers that can be resolved by the DI container.
HTTP Request message abstraction
Inversify HTTP decorators can be used to abstract HTTP request details, such as headers, query parameters, and request bodies. This allows for cleaner controller code and better separation of concerns.
Body
Reads the HTTP request body, or a specific property of it when a name is provided. The type and shape of the body depends on the Content-Type header of the request and the adapter being used.
Body(
optionsOrPipe?: RouteParameterOptions | (ServiceIdentifier<Pipe> | Pipe),
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
optionsOrPipe(optional): Either route parameter options or a pipe. When this isRouteParameterOptions, it contains configuration likeparameterName. When it's a pipe (service identifier or instance), it's the first pipe in the pipeline.parameterPipeList: Additional pipes to apply to the parameter for transformation and validation.
Content Type Handling
The @Body() decorator automatically parses the request body based on the Content-Type header. Different adapters may require specific configuration options to be enabled.
JSON (application/json)
All adapters support JSON parsing by default (though Express adapters may require the useJson option to be enabled).
@Controller('/users')
export class BodyJsonController {
@Post()
public async createUser(@Body() body: UserPayload): Promise<UserResult> {
// Body is automatically parsed from JSON when Content-Type is application/json
return {
email: body.email,
id: 1,
name: body.name,
};
}
}
URL-encoded Form Data (application/x-www-form-urlencoded)
Form data is parsed into a Record<string, string | string[]> object. Express and Fastify adapters require specific options to be enabled:
- Express/Express v4: Set
useUrlEncoded: truein adapter options - Fastify: Set
useFormUrlEncoded: truein adapter options - Hono/uWebSockets.js: Supported by default
@Controller('/auth')
export class BodyUrlEncodedController {
@Post('/login')
public async login(
@Body() body: Record<string, string | string[]>,
): Promise<FormResult> {
// Body is automatically parsed from URL-encoded form data
// when Content-Type is application/x-www-form-urlencoded
return {
password: body['password'] as string,
username: body['username'] as string,
};
}
}
Plain Text (text/plain, text/*)
Text bodies are received as strings. Express adapters require the useText: true option to be enabled.
@Controller('/documents')
export class BodyTextController {
@Post()
public async createDocument(@Body() body: string): Promise<TextResult> {
// Body is received as a string when Content-Type is text/plain or text/*
return {
content: body,
length: body.length,
};
}
}
Multipart Form Data (multipart/form-data)
Multipart form data handling varies significantly between adapters. The body type and parsing behavior is adapter-specific:
Fastify
Enable with useMultipartFormData: true (or pass multipart options) in adapter options. Returns an AsyncIterableIterator<Multipart>.
@Controller('/uploads')
export class BodyMultipartFastifyController {
@Post()
public async uploadFile(
// Type would be: AsyncIterableIterator<Multipart> | undefined
@Body() body: AsyncIterableIterator<unknown> | undefined,
): Promise<UploadResult> {
// With Fastify, multipart/form-data returns an async iterator of Multipart objects
// when useMultipartFormData option is enabled
let fileName: string = '';
let username: string = '';
if (body !== undefined) {
for await (const field of body) {
const multipartField: {
fieldname: string;
filename: string;
type: string;
value: unknown;
} = field as {
fieldname: string;
filename: string;
type: string;
value: unknown;
};
if (multipartField.type === 'file') {
fileName = multipartField.filename;
} else if (multipartField.fieldname === 'username') {
username = multipartField.value as string;
}
}
}
return { fileName, username };
}
}
Hono
Multipart is supported by default. Returns a FormData object.
@Controller('/uploads')
export class BodyMultipartHonoController {
@Post()
public async uploadFile(@Body() body: FormData): Promise<UploadResult> {
// With Hono, multipart/form-data is automatically parsed into FormData
const file: File | string | null = body.get('file');
const fileName: string = file instanceof File ? file.name : '';
const username: string = (body.get('username') as string | null) ?? '';
return { fileName, username };
}
}
uWebSockets.js
Multipart is supported by default. Returns a MultipartField[] array.
@Controller('/uploads')
export class BodyMultipartUwebsocketsController {
@Post()
public async uploadFile(
// Type would be: MultipartField[] | undefined
@Body() body: unknown[] | undefined,
): Promise<UploadResult> {
// With uWebSockets.js, multipart/form-data is parsed into an array of MultipartField objects
let fileName: string = '';
let username: string = '';
if (body !== undefined) {
const textDecoder: TextDecoder = new TextDecoder();
for (const field of body) {
const multipartField: {
name: string;
filename?: string;
data: Uint8Array;
} = field as {
name: string;
filename?: string;
data: Uint8Array;
};
if (multipartField.filename !== undefined) {
fileName = multipartField.filename;
} else if (multipartField.name === 'username') {
username = textDecoder.decode(multipartField.data);
}
}
}
return { fileName, username };
}
}
Express/Express v4
Express adapters do not handle multipart/form-data out of the box. You must register a multipart middleware (such as multer) on your Express app before building the InversifyJS HTTP server.
import { Controller, Post, Request } from '@inversifyjs/http-core';
import type express from 'express';
export interface MultipartResult {
fileCount: number;
username: string;
}
/**
* Example showing multipart/form-data handling with Express adapters.
*
* Express adapters require multer middleware to be registered on the Express app
* before building the InversifyJS HTTP server.
*
* With multer, files are in request.files and text fields are in request.body.
*/
@Controller('/users')
export class MultipartExpressController {
@Post()
public createUser(@Request() request: express.Request): MultipartResult {
// With multer's .any(), file uploads are in request.files
// and text form fields are in request.body
const files: Express.Multer.File[] = (request.files ??
[]) as Express.Multer.File[];
const body: { username?: string } = request.body as {
username?: string;
};
return {
fileCount: files.length,
username: body.username ?? '',
};
}
}
Notes
- The
@Body()decorator is asynchronous for some adapters (Fastify, Hono, uWebSockets.js). The framework handles this automatically. - When using
parameterName, the decorator extracts a specific property from the parsed body object (not supported for multipart bodies that return iterators or FormData). - Always validate and sanitize body input using pipes or validation libraries to ensure security.
Headers
Reads HTTP request headers, or a specific header when a name is provided.
Headers(
optionsOrPipe?: RouteParameterOptions | (ServiceIdentifier<Pipe> | Pipe),
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
optionsOrPipe(optional): Either route parameter options or a pipe. When this isRouteParameterOptions, it contains configuration like headername. When it's a pipe (service identifier or instance), it's the first pipe in the pipeline.parameterPipeList: Additional pipes to apply to the parameter for transformation and validation.
Example: reading the User-Agent header
@Controller('/headers')
export class HeadersController {
@Get()
public async getUserAgent(
@Headers({
name: 'x-client',
})
userAgent: string | undefined,
): Promise<HeadersResult> {
return {
agent: userAgent,
};
}
}
Cookies
Reads HTTP cookies, or a specific cookie when a name is provided.
Remember to enable useCookies option when using express or fastify adapters.
Cookies(
optionsOrPipe?: RouteParameterOptions | (ServiceIdentifier<Pipe> | Pipe),
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
optionsOrPipe(optional): Either route parameter options or a pipe. When this isRouteParameterOptions, it contains configuration like cookiename. When it's a pipe (service identifier or instance), it's the first pipe in the pipeline.parameterPipeList: Additional pipes to apply to the parameter for transformation and validation.
Example: reading a session cookie
@Controller('/cookies')
export class CookiesController {
@Get()
public async getCookie(
@Cookies({
name: 'sessionId',
})
sessionId: string | undefined,
): Promise<CookiesResult> {
return {
sessionId,
};
}
}
Params
Reads HTTP route parameters (path parameters), or a specific parameter when a name is provided.
Params(
optionsOrPipe?: RouteParameterOptions | (ServiceIdentifier<Pipe> | Pipe),
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
optionsOrPipe(optional): Either route parameter options or a pipe. When this isRouteParameterOptions, it contains configuration like parametername. When it's a pipe (service identifier or instance), it's the first pipe in the pipeline.parameterPipeList: Additional pipes to apply to the parameter for transformation and validation.
Request
Retrieves the native request object of the current adapter. Useful when you need full access to the underlying framework request.
Request(...pipeList: (ServiceIdentifier<Pipe> | Pipe)[]): ParameterDecorator
Example: accessing the native request per adapter
- Express 4
- Express 5
- Fastify
- Hono
- uWebSockets.js
@Controller('/headers')
export class RequestExpressController {
@Get()
public async readHeader(
@Request() request: express.Request,
): Promise<string> {
const value: string | string[] | undefined =
request.headers['x-test-header'];
let parsedValue: string;
if (Array.isArray(value)) {
if (value.length !== 1) {
throw new BadRequestHttpResponse(
{ message: 'Expected a single `x-test-header` value' },
'Expected a single `x-test-header` value',
);
}
[parsedValue] = value as [string];
} else {
parsedValue = value ?? '';
}
return parsedValue;
}
}
@Controller('/headers')
export class RequestExpressController {
@Get()
public async readHeader(
@Request() request: express.Request,
): Promise<string> {
const value: string | string[] | undefined =
request.headers['x-test-header'];
let parsedValue: string;
if (Array.isArray(value)) {
if (value.length !== 1) {
throw new BadRequestHttpResponse(
{ message: 'Expected a single `x-test-header` value' },
'Expected a single `x-test-header` value',
);
}
[parsedValue] = value as [string];
} else {
parsedValue = value ?? '';
}
return parsedValue;
}
}
@Controller('/headers')
export class RequestFastifyController {
@Get()
public async readHeader(@Request() request: FastifyRequest): Promise<string> {
const value: string | string[] | undefined =
request.headers['x-test-header'];
let parsedValue: string;
if (Array.isArray(value)) {
if (value.length !== 1) {
throw new BadRequestHttpResponse(
{ message: 'Expected a single `x-test-header` value' },
'Expected a single `x-test-header` value',
);
}
[parsedValue] = value as [string];
} else {
parsedValue = value ?? '';
}
return parsedValue;
}
}
@Controller('/headers')
export class RequestHonoController {
@Get()
public async readHeader(@Request() request: HonoRequest): Promise<string> {
const value: string | undefined = request.header('x-test-header');
return value ?? '';
}
}
@Controller('/headers')
export class RequestUwebsocketsController {
@Get()
public async readHeader(@Request() request: HttpRequest): Promise<string> {
const value: string = request.getHeader('x-test-header');
return value;
}
}
Query
Reads HTTP query parameters, or a specific query parameter when a name is provided.
Query(
optionsOrPipe?: RouteParameterOptions | (ServiceIdentifier<Pipe> | Pipe),
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
optionsOrPipe(optional): Either route parameter options or a pipe. When this isRouteParameterOptions, it contains configuration like parametername. When it's a pipe (service identifier or instance), it's the first pipe in the pipeline.parameterPipeList: Additional pipes to apply to the parameter for transformation and validation.
HTTP Response message abstraction
Inversify HTTP decorators can also be used to abstract HTTP response details, such as status codes and headers. This allows for more consistent and maintainable response handling across controllers.
Response
Injects the native response object of the current adapter. Use it when you want to write directly to the underlying framework response.
Response(...pipeList: (ServiceIdentifier<Pipe> | Pipe)[]): ParameterDecorator
Notes
- Type-safe: Type the parameter with the adapter's response type for intellisense and safety.
- Return type: When you write to the native response, prefer a return type of
void. For Hono, return theResponseproduced by theContext. - Hono: Prefer using
@Context()from@inversifyjs/http-honoto access Hono'sContextobject.
Example: sending a response per adapter
- Express 4
- Express 5
- Fastify
- Hono
- uWebSockets.js
@Controller('/message')
export class ResponseExpressController {
@Get()
public async sendMessage(
@Response() response: express.Response,
): Promise<void> {
response.send({ message: 'hello' });
}
}
@Controller('/message')
export class ResponseExpressController {
@Get()
public async sendMessage(
@Response() response: express.Response,
): Promise<void> {
response.send({ message: 'hello' });
}
}
@Controller('/message')
export class ResponseFastifyController {
@Get()
public async sendMessage(@Response() reply: FastifyReply): Promise<void> {
reply.send({ message: 'hello' });
}
}
@inversifyjs/http-hono provides the @Context() decorator to access the Context object.
import { Controller, Get } from '@inversifyjs/http-core';
import { Context } from '@inversifyjs/http-hono';
import { type Context as HonoContext } from 'hono';
@Controller('/message')
export class ResponseHonoController {
@Get()
public async sendMessage(@Context() context: HonoContext): Promise<Response> {
return context.json({ message: 'hello' });
}
}
@Controller('/message')
export class ResponseUwebsocketsController {
@Get()
public sendMessage(@Response() response: HttpResponse): void {
response.cork((): void => {
response.writeHeader('Content-Type', 'application/json');
response.end(JSON.stringify({ message: 'hello' }));
});
}
}
SetHeader
Sets a HTTP response header.
SetHeader(headerKey: string, value: string): MethodDecorator
Parameters
headerKey(string): Name of the header to be set.value(string): Value of the header to be set.
Example: Setting a response header
@Controller('/messages')
export class ContentController {
@Post()
@SetHeader('custom-content-header', 'sample')
public async createMessage(@Body() body: BodyPayload): Promise<BodyResult> {
return {
message: body.message,
};
}
}
StatusCode
Sets the HTTP response status code.
StatusCode(statusCode: HttpStatusCode): MethodDecorator
Parameters
statusCode(HttpStatusCode): Status code to be set.
Example: Setting a response status code
@Controller('/messages')
export class ContentController {
@Post()
@StatusCode(HttpStatusCode.CREATED)
public async createMessage(@Body() body: BodyPayload): Promise<BodyResult> {
return {
message: body.message,
};
}
}
Controller
The @Controller decorator marks a class as an HTTP controller and allows you to configure its base path, priority, scope, and service identifier.
Controller(pathOrOptions?: string | ControllerOptions): ClassDecorator
Parameters
pathOrOptions(optional): Either a string representing the base path, or aControllerOptionsobject with the following properties:path(optional string): Base path for all routes in this controller. Defaults to'/'.priority(optional number): Registration priority. Controllers with higher priority values are registered before those with lower values. Defaults to0. Useful for controlling route evaluation order, such as ensuring wildcard fallback routes are registered last.scope(optional BindingScope): InversifyJS binding scope (Request,Singleton, orTransient).serviceIdentifier(optional ServiceIdentifier): Custom service identifier for dependency injection.
Notes
- Route registration order: Controllers are registered in descending order of priority (highest priority first). This is particularly useful when you need specific routes to be evaluated before more general ones (e.g., ensuring
/api/usersis checked before a wildcard/*fallback). - Default priority: Controllers without an explicit priority are assigned
0.
Example: Controller with priority
- Express 4
- Express 5
- Fastify
- Hono
- uWebSockets.js
const PRIORITY_HIGHEST: number = 1000;
const PRIORITY_DEFAULT: number = 0;
const PRIORITY_LOWEST: number = -1000;
const HTTP_STATUS_CREATED: number = 201;
const HTTP_STATUS_NOT_FOUND: number = 404;
// High priority controller - registered first
@Controller({
path: '/api/messages',
priority: PRIORITY_HIGHEST,
})
export class MessagesController {
@Get()
public async getMessages(): Promise<Message[]> {
return [{ content: 'Hello, World!' }];
}
@Post()
@StatusCode(HTTP_STATUS_CREATED)
public async createMessage(
@Body() body: CreateMessageRequest,
): Promise<Message> {
return { content: body.content };
}
}
// Default priority controller
@Controller({
path: '/api/health',
priority: PRIORITY_DEFAULT,
})
export class HealthController {
@Get()
public async healthCheck(): Promise<{ status: string }> {
return { status: 'ok' };
}
}
// Low priority fallback controller - registered last to catch unmatched routes
@Controller({
priority: PRIORITY_LOWEST,
})
export class FallbackController {
@Get('*')
@StatusCode(HTTP_STATUS_NOT_FOUND)
public async notFound(): Promise<{ error: string; message: string }> {
return {
error: 'Not Found',
message: 'The requested resource was not found',
};
}
}
const PRIORITY_HIGHEST: number = 1000;
const PRIORITY_DEFAULT: number = 0;
const PRIORITY_LOWEST: number = -1000;
const HTTP_STATUS_CREATED: number = 201;
const HTTP_STATUS_NOT_FOUND: number = 404;
// High priority controller - registered first
@Controller({
path: '/api/messages',
priority: PRIORITY_HIGHEST,
})
export class MessagesController {
@Get()
public async getMessages(): Promise<Message[]> {
return [{ content: 'Hello, World!' }];
}
@Post()
@StatusCode(HTTP_STATUS_CREATED)
public async createMessage(
@Body() body: CreateMessageRequest,
): Promise<Message> {
return { content: body.content };
}
}
// Default priority controller
@Controller({
path: '/api/health',
priority: PRIORITY_DEFAULT,
})
export class HealthController {
@Get()
public async healthCheck(): Promise<{ status: string }> {
return { status: 'ok' };
}
}
// Low priority fallback controller - registered last to catch unmatched routes
@Controller({
priority: PRIORITY_LOWEST,
})
export class FallbackController {
@Get('/{*any}')
@StatusCode(HTTP_STATUS_NOT_FOUND)
public async notFound(): Promise<{ error: string; message: string }> {
return {
error: 'Not Found',
message: 'The requested resource was not found',
};
}
}
const PRIORITY_HIGHEST: number = 1000;
const PRIORITY_DEFAULT: number = 0;
const PRIORITY_LOWEST: number = -1000;
const HTTP_STATUS_CREATED: number = 201;
const HTTP_STATUS_NOT_FOUND: number = 404;
// High priority controller - registered first
@Controller({
path: '/api/messages',
priority: PRIORITY_HIGHEST,
})
export class MessagesController {
@Get()
public async getMessages(): Promise<Message[]> {
return [{ content: 'Hello, World!' }];
}
@Post()
@StatusCode(HTTP_STATUS_CREATED)
public async createMessage(
@Body() body: CreateMessageRequest,
): Promise<Message> {
return { content: body.content };
}
}
// Default priority controller
@Controller({
path: '/api/health',
priority: PRIORITY_DEFAULT,
})
export class HealthController {
@Get()
public async healthCheck(): Promise<{ status: string }> {
return { status: 'ok' };
}
}
// Low priority fallback controller - registered last to catch unmatched routes
@Controller({
priority: PRIORITY_LOWEST,
})
export class FallbackController {
@Get('*')
@StatusCode(HTTP_STATUS_NOT_FOUND)
public async notFound(): Promise<{ error: string; message: string }> {
return {
error: 'Not Found',
message: 'The requested resource was not found',
};
}
}
const PRIORITY_HIGHEST: number = 1000;
const PRIORITY_DEFAULT: number = 0;
const PRIORITY_LOWEST: number = -1000;
const HTTP_STATUS_CREATED: number = 201;
const HTTP_STATUS_NOT_FOUND: number = 404;
// High priority controller - registered first
@Controller({
path: '/api/messages',
priority: PRIORITY_HIGHEST,
})
export class MessagesController {
@Get()
public async getMessages(): Promise<Message[]> {
return [{ content: 'Hello, World!' }];
}
@Post()
@StatusCode(HTTP_STATUS_CREATED)
public async createMessage(
@Body() body: CreateMessageRequest,
): Promise<Message> {
return { content: body.content };
}
}
// Default priority controller
@Controller({
path: '/api/health',
priority: PRIORITY_DEFAULT,
})
export class HealthController {
@Get()
public async healthCheck(): Promise<{ status: string }> {
return { status: 'ok' };
}
}
// Low priority fallback controller - registered last to catch unmatched routes
@Controller({
priority: PRIORITY_LOWEST,
})
export class FallbackController {
@Get('*')
@StatusCode(HTTP_STATUS_NOT_FOUND)
public async notFound(): Promise<{ error: string; message: string }> {
return {
error: 'Not Found',
message: 'The requested resource was not found',
};
}
}
const PRIORITY_HIGHEST: number = 1000;
const PRIORITY_DEFAULT: number = 0;
const PRIORITY_LOWEST: number = -1000;
const HTTP_STATUS_CREATED: number = 201;
const HTTP_STATUS_NOT_FOUND: number = 404;
// High priority controller - registered first
@Controller({
path: '/api/messages',
priority: PRIORITY_HIGHEST,
})
export class MessagesController {
@Get()
public async getMessages(): Promise<Message[]> {
return [{ content: 'Hello, World!' }];
}
@Post()
@StatusCode(HTTP_STATUS_CREATED)
public async createMessage(
@Body() body: CreateMessageRequest,
): Promise<Message> {
return { content: body.content };
}
}
// Default priority controller
@Controller({
path: '/api/health',
priority: PRIORITY_DEFAULT,
})
export class HealthController {
@Get()
public async healthCheck(): Promise<{ status: string }> {
return { status: 'ok' };
}
}
// Low priority fallback controller - registered last to catch unmatched routes
@Controller({
priority: PRIORITY_LOWEST,
})
export class FallbackController {
@Get('*')
@StatusCode(HTTP_STATUS_NOT_FOUND)
public async notFound(): Promise<{ error: string; message: string }> {
return {
error: 'Not Found',
message: 'The requested resource was not found',
};
}
}
Custom decorators
createCustomParameterDecorator
createCustomParameterDecorator allows you to define your own decorators to be used in route handler parameters.
createCustomParameterDecorator<TRequest, TResponse, TResult>(
handler: CustomParameterDecoratorHandler<TRequest, TResponse, TResult>,
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
handler(optional): Parameter decorator handler. It takes arequest, aresponseandCustomParameterDecoratorHandlerOptionsthat can be used to read request values or write response status code or headers.parameterPipeList(optional): List of pipes to be applied.
Example: user custom decorator
Assuming an auth middleware lets user details in a user property of a given request, we can easily create a custom decorator to provide such user details as follows:
const User: () => ParameterDecorator = () =>
createCustomParameterDecorator((request: UserContext): User => request.user);
@Controller('/api/users')
export class UsersController {
@ApplyMiddleware(AuthMiddleware)
@Get('me')
public getMe(@User() me: User): User {
return me;
}
}
createCustomNativeParameterDecorator
createCustomNativeParameterDecorator allows you to define your own decorators to be used in route handler parameters.
Using a custom native parameter decorator in a route handler forces the native mode.
createCustomNativeParameterDecorator<
TRequest,
TResponse,
TDecoratorResult,
TResult,
>(
handler: CustomNativeParameterDecoratorHandler<
TRequest,
TResponse,
TDecoratorResult,
TResult
>,
...parameterPipeList: (ServiceIdentifier<Pipe> | Pipe)[]
): ParameterDecorator
Parameters
handler(optional): Parameter decorator handler. It takes arequest, aresponseandCustomNativeParameterDecoratorHandlerOptionsthat can be used to read request values or write response status code, headers or body.parameterPipeList(optional): List of pipes to be applied.
Example: uWebSockets file stream decorator
Streams are often used in http frameworks to handle large files or data transfers efficiently. Using a stream has its downsides. For example, there's no way to tell what's the current size of the file if we just pass it as a stream. No Content-Length header is expected in the http response either. To solve this, we can create a custom decorator that reads the file metadata and uses uWebSockets.js's native response methods to set the appropriate headers before streaming the file content, avoiding using chunked transfer encoding in favor of providing Content-Length.
Since we are using the decorator to write directly a response body, we need to force the native mode by using createCustomNativeParameterDecorator.
Otherwise, the framework would try to send status codes and headers after the body has been written, which is simply not possible due to the nature of HTTP.
const FileStream: () => ParameterDecorator = () =>
createCustomNativeParameterDecorator<
HttpRequest,
HttpResponse,
(options: FileStreamOptions) => void,
undefined
>(
(
_request: HttpRequest,
response: HttpResponse,
options: CustomNativeParameterDecoratorHandlerOptions<
HttpRequest,
HttpResponse,
undefined
>,
): ((streamOptions: FileStreamOptions) => Promise<void>) => {
return async (streamOptions: FileStreamOptions): Promise<void> => {
const { contentType, filePath }: FileStreamOptions = streamOptions;
// Get file size for Content-Length header
const fileSize: number = (await stat(filePath)).size;
// Set response headers
options.setStatus(_request, response, HttpStatusCode.OK);
options.setHeader(
_request,
response,
'content-length',
fileSize.toString(),
);
options.setHeader(
_request,
response,
'content-type',
contentType ?? 'application/octet-stream',
);
const fileStream: Readable = createReadStream(filePath);
// Use uWebSockets.js native streaming with known size
// This avoids chunked transfer encoding by providing Content-Length
pipeKnownSizeStreamOverResponse(
response,
fileStream,
fileSize,
undefined,
);
};
},
);
@Controller('/files')
export class FileStreamController {
@Get(':filename')
public async streamFile(
// SECURITY: Always sanitize your inputs. Validate filename to prevent path traversal attacks
@Params({ name: 'filename' }, ValidFilenamePipe) filename: string,
@FileStream() stream: (options: FileStreamOptions) => Promise<void>,
): Promise<void> {
// Stream the file with appropriate content type
await stream({
contentType: 'video/mp4',
filePath: `/var/www/files/${filename}`,
});
}
}
HTTP methods
All
Sets up a route for all HTTP methods.
All(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. It binds the method to all HTTP methods for the given path.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@All()
public async allContent(): Promise<void> {}
}
Delete
Sets up a route for DELETE requests.
Delete(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. Example:@Delete('/remove')maps toDELETE <controllerPath>/remove.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Delete()
public async deleteContent(
@Query() queryParams: { content: string },
): Promise<Content> {
return {
content: queryParams.content,
};
}
}
Get
Sets up a route for GET requests.
Get(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. Example:@Get('/list')maps toGET <controllerPath>/list. When omitted, the method is bound to the controller path itself.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Get()
public async getContent(
@Query() queryParams: { content: string },
): Promise<Content> {
return {
content: queryParams.content,
};
}
}
Head
Sets up a route for HEAD requests.
Since hono@4, HEAD routes are no longer allowed, use @Get routes instead if you rely on the Hono adapter.
Head(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. Example:@Head('/')maps toHEAD <controllerPath>.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Head()
@SetHeader('custom-content-header', 'sample')
public async headContent(): Promise<undefined> {
return undefined;
}
}
Options
Sets up a route for OPTIONS requests.
Options(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. Example:@Options('/')maps toOPTIONS <controllerPath>.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Options()
public async optionsContent(): Promise<undefined> {
return undefined;
}
}
Patch
Sets up a route for PATCH requests.
Patch(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. Example:@Patch('/partial')maps toPATCH <controllerPath>/partial.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Patch()
public async patchContent(
@Body() body: { content: string },
): Promise<Content> {
return {
content: body.content,
};
}
}
Post
Sets up a route for POST requests.
Post(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to'/'. Example:@Post('/create')maps toPOST <controllerPath>/create.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Post()
public async createContent(
@Body() body: { content: string },
): Promise<Content> {
return {
content: body.content,
};
}
}
Put
Sets up a route for PUT requests.
Put(path?: string): MethodDecorator
Parameters
path(optional string): Route path mounted under the controller's base path. Defaults to '/'. Example: @Put('/update') maps to PUT <controllerPath>/update.
Notes
- Use @Body to read the request payload for PUT.
Example: decorating a controller method
@Controller('/content')
export class ContentController {
@Put()
public async updateContent(
@Body() body: { content: string },
): Promise<Content> {
return {
content: body.content,
};
}
}