Utils
createRouteValueMetadataUtils
createRouteValueMetadataUtils is a factory function that creates a [decorator, getter] pair for attaching custom key-value metadata to routes and reading it from the request at runtime. This is useful for scenarios like role-based access control, rate limiting, feature flags, or any custom per-route configuration that middleware or guards need to access.
Each adapter package exports its own createRouteValueMetadataUtils with a getter typed to the adapter's native request type.
function createRouteValueMetadataUtils<T>(
key: string | symbol,
): [
decorator: (value: T) => MethodDecorator,
getter: (request: Request) => T | undefined,
]
Parameters
key: A unique string or symbol that identifies the metadata entry. Each metadata key should be unique across your application to avoid collisions.
Returns
A tuple with two elements:
decorator: A method decorator factory. Call it with a value to create a decorator that attaches that value as metadata on a controller method.getter: A function that retrieves the metadata value from the request object. Returnsundefinedif the metadata is not present on the current route.
:::info How it works
The decorator stores metadata on the controller class using reflect-metadata. At request time, the adapter's internal middleware populates the metadata on the request object before any user-defined middleware, guards, or interceptors execute. The getter reads from this request-level storage.
:::
Example
The following example demonstrates creating a Roles decorator and getRoles getter, then using them in a middleware to read route-level role metadata and set a response header.
- Express 4
- Express 5
- Fastify
- Hono
- uWebSockets.js
import { ApplyMiddleware, Controller, Get } from '@inversifyjs/http-core';
import {
createRouteValueMetadataUtils,
type ExpressMiddleware,
} from '@inversifyjs/http-express-v4';
import { type NextFunction, type Request, type Response } from 'express4';
// eslint-disable-next-line @typescript-eslint/naming-convention
const [Roles, getRoles]: [
decorator: (value: string[]) => MethodDecorator,
getter: (request: Request) => string[] | undefined,
] = createRouteValueMetadataUtils<string[]>('ROLES');
export class RolesMiddleware implements ExpressMiddleware {
public execute(
request: Request,
response: Response,
next: NextFunction,
): void {
const roles: string[] | undefined = getRoles(request);
if (roles !== undefined) {
response.setHeader('x-route-roles', roles.join(','));
}
next();
}
}
@Controller('/users')
export class UsersController {
@Roles(['admin', 'user'])
@ApplyMiddleware(RolesMiddleware)
@Get()
public async getUsers(): Promise<string> {
return 'users';
}
}
import { ApplyMiddleware, Controller, Get } from '@inversifyjs/http-core';
import {
createRouteValueMetadataUtils,
type ExpressMiddleware,
} from '@inversifyjs/http-express';
import { type NextFunction, type Request, type Response } from 'express';
// eslint-disable-next-line @typescript-eslint/naming-convention
const [Roles, getRoles]: [
decorator: (value: string[]) => MethodDecorator,
getter: (request: Request) => string[] | undefined,
] = createRouteValueMetadataUtils<string[]>('ROLES');
export class RolesMiddleware implements ExpressMiddleware {
public execute(
request: Request,
response: Response,
next: NextFunction,
): void {
const roles: string[] | undefined = getRoles(request);
if (roles !== undefined) {
response.setHeader('x-route-roles', roles.join(','));
}
next();
}
}
@Controller('/users')
export class UsersController {
@Roles(['admin', 'user'])
@ApplyMiddleware(RolesMiddleware)
@Get()
public async getUsers(): Promise<string> {
return 'users';
}
}
import { ApplyMiddleware, Controller, Get } from '@inversifyjs/http-core';
import {
createRouteValueMetadataUtils,
type FastifyMiddleware,
} from '@inversifyjs/http-fastify';
import { type FastifyReply, type FastifyRequest } from 'fastify';
// eslint-disable-next-line @typescript-eslint/naming-convention
const [Roles, getRoles]: [
decorator: (value: string[]) => MethodDecorator,
getter: (request: FastifyRequest) => string[] | undefined,
] = createRouteValueMetadataUtils<string[]>('ROLES');
export class RolesMiddleware implements FastifyMiddleware {
public execute(
request: FastifyRequest,
response: FastifyReply,
next: () => void,
): void {
const roles: string[] | undefined = getRoles(request);
if (roles !== undefined) {
response.header('x-route-roles', roles.join(','));
}
next();
}
}
@Controller('/users')
export class UsersController {
@Roles(['admin', 'user'])
@ApplyMiddleware(RolesMiddleware)
@Get()
public async getUsers(): Promise<string> {
return 'users';
}
}
import { ApplyMiddleware, Controller, Get } from '@inversifyjs/http-core';
import {
createRouteValueMetadataUtils,
type HonoMiddleware,
} from '@inversifyjs/http-hono';
import { type Context, type HonoRequest, type Next } from 'hono';
// eslint-disable-next-line @typescript-eslint/naming-convention
const [Roles, getRoles]: [
decorator: (value: string[]) => MethodDecorator,
getter: (request: HonoRequest) => string[] | undefined,
] = createRouteValueMetadataUtils<string[]>('ROLES');
export class RolesMiddleware implements HonoMiddleware {
public async execute(
request: HonoRequest,
context: Context,
next: Next,
): Promise<Response | undefined> {
const roles: string[] | undefined = getRoles(request);
if (roles !== undefined) {
context.header('x-route-roles', roles.join(','));
}
await next();
return undefined;
}
}
@Controller('/users')
export class UsersController {
@Roles(['admin', 'user'])
@ApplyMiddleware(RolesMiddleware)
@Get()
public async getUsers(): Promise<string> {
return 'users';
}
}
import { ApplyMiddleware, Controller, Get } from '@inversifyjs/http-core';
import {
createRouteValueMetadataUtils,
type UwebSocketsMiddleware,
} from '@inversifyjs/http-uwebsockets';
import { type HttpRequest, type HttpResponse } from 'uWebSockets.js';
// eslint-disable-next-line @typescript-eslint/naming-convention
const [Roles, getRoles]: [
decorator: (value: string[]) => MethodDecorator,
getter: (request: HttpRequest) => string[] | undefined,
] = createRouteValueMetadataUtils<string[]>('ROLES');
export class RolesMiddleware implements UwebSocketsMiddleware {
public execute(
request: HttpRequest,
response: HttpResponse,
next: () => void,
): void {
const roles: string[] | undefined = getRoles(request);
if (roles !== undefined) {
response.cork((): void => {
response.writeHeader('x-route-roles', roles.join(','));
});
}
next();
}
}
@Controller('/users')
export class UsersController {
@Roles(['admin', 'user'])
@ApplyMiddleware(RolesMiddleware)
@Get()
public async getUsers(): Promise<string> {
return 'users';
}
}
- Route value metadata is read-only at request time — it is set once at route registration and cannot be mutated during request handling.
- Metadata is only available at the method level. To share metadata across all routes in a controller, apply the decorator to each method individually.
- The metadata middleware is prepended before any user-defined middleware, so the getter is available in
@ApplyMiddlewarehandlers, guards, and interceptors.