@nextrush/decorators
Controller, route, parameter, and guard decorators for structured APIs.
Build HTTP controllers with TypeScript decorators.
This package provides decorators for defining controllers, routes, parameters, and guards. It generates metadata that the controllers plugin uses to wire routes automatically.
Installation
$ pnpm add @nextrush/decorators
Required tsconfig.json settings:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}reflect-metadata
The nextrush meta-package auto-imports reflect-metadata. If using individual @nextrush/*
packages, install reflect-metadata separately and import it as the first line of your entry
point.
Quick Start
import 'reflect-metadata'; // Not needed if using the nextrush meta-package
import { Controller, Get, Post, Body, Param } from '@nextrush/decorators';
@Controller('/users')
class UserController {
@Get()
list() {
return [{ id: 1, name: 'Alice' }];
}
@Get('/:id')
findOne(@Param('id') id: string) {
return { id, name: 'Alice' };
}
@Post()
create(@Body() data: { name: string }) {
return { id: Date.now(), ...data };
}
}Controller Decorator
Define a controller class with a path prefix or a full options object:
// Simple prefix
@Controller('/users')
class UserController {}
// With options
@Controller({
path: '/api/v1/users',
version: 'v1',
tags: ['users'],
})
class VersionedUserController {}ControllerOptions
| Property | Type | Description |
|---|---|---|
path? | string | Base path prefix for all routes in this controller |
version? | string | API version prefix (e.g., 'v1' → '/v1/users') |
middleware? | MiddlewareRef[] | Middleware applied to all routes in this controller |
tags? | string[] | Tags for documentation grouping |
Route Decorators
Map methods to HTTP endpoints:
@Controller('/items')
class ItemController {
@Get() // GET /items
list() {}
@Get('/:id') // GET /items/:id
findOne() {}
@Post() // POST /items
create() {}
@Put('/:id') // PUT /items/:id
replace() {}
@Patch('/:id') // PATCH /items/:id
update() {}
@Delete('/:id') // DELETE /items/:id
remove() {}
@Head() // HEAD /items
head() {}
@Options() // OPTIONS /items
options() {}
@All('/hook') // All methods on /items/hook
webhook() {}
}Parameter Decorators
Extract data from requests:
@Body
Get request body:
@Post()
create(@Body() data: CreateUserDto) {
// data = full parsed body
}
@Post()
createWithField(@Body('name') name: string) {
// name = body.name
}@Param
Get route parameters:
@Get('/:id')
findOne(@Param('id') id: string) {
// id = route param :id
}
@Get('/:category/:id')
findByCategory(@Param() params: { category: string; id: string }) {
// params = all route params
}@Query
Get query parameters:
@Get()
list(@Query('page') page: string, @Query('limit') limit: string) {
// ?page=1&limit=10
}
@Get()
search(@Query() query: Record<string, string>) {
// query = all query params
}@Header
Get request headers:
@Get()
info(@Header('authorization') auth: string) {
// auth = Authorization header
}
@Get()
allHeaders(@Header() headers: Record<string, string>) {
// headers = all headers
}@Ctx
Get full context:
import type { Context } from '@nextrush/types';
@Get()
handler(@Ctx() ctx: Context) {
// Full access to request/response
}@Req / @Res
Get raw request/response (Node.js):
@Get()
raw(@Req() req: IncomingMessage, @Res() res: ServerResponse) {
// Raw Node.js objects
}Parameter Transforms
Transform parameter values:
// Convert to number
@Get('/:id')
findOne(@Param('id', { transform: Number }) id: number) {}
// Validate with Zod
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
@Post()
create(@Body({ transform: CreateUserSchema.parse }) data: z.infer<typeof CreateUserSchema>) {}Response Decorators
@SetHeader
Set response headers on a route. Multiple headers can be applied by stacking decorators.
import { SetHeader, Controller, Get } from '@nextrush/decorators';
@Controller('/api')
class ApiController {
@SetHeader('X-Custom-Header', 'my-value')
@SetHeader('Cache-Control', 'no-store')
@Get('/data')
getData() {
return { result: 'ok' };
}
}Headers are precomputed at build time and applied before the handler sends a response.
@Redirect
Redirect the request to another URL. Default status code is 302 (Found).
import { Redirect, Controller, Get } from '@nextrush/decorators';
@Controller('/legacy')
class LegacyController {
@Redirect('/new-dashboard', 301)
@Get('/dashboard')
oldDashboard() {
// Handler return value is ignored when @Redirect is applied
}
@Redirect('/default-page')
@Get('/home')
home() {
// Return a string to override the redirect URL
return '/custom-page';
}
@Redirect('/fallback')
@Get('/dynamic')
dynamic() {
// Return { url?, statusCode? } to override both
return { url: '/new-location', statusCode: 307 };
}
}Override behavior:
- Return
void→ uses the URL and status code from the decorator - Return
string→ overrides the redirect URL - Return
{ url?, statusCode? }→ overrides URL and/or status code
The redirect is implemented via Location header, not ctx.redirect().
Custom Parameter Decorators
Create reusable parameter decorators with createCustomParamDecorator(). This is the public API for extending the parameter system.
import { createCustomParamDecorator } from '@nextrush/decorators';
import type { Context } from '@nextrush/types';
// Extract current user from state (set by auth middleware)
const CurrentUser = createCustomParamDecorator((ctx: Context) => ctx.state.user);
// With options
const ApiKey = createCustomParamDecorator((ctx: Context) => ctx.get('x-api-key'), {
required: true,
});
// With transform
const ParsedQuery = (name: string) =>
createCustomParamDecorator((ctx: Context) => ctx.query[name], { transform: JSON.parse });
@Controller('/users')
class UserController {
@Get('/me')
getProfile(@CurrentUser user: User) {
return user;
}
@Get('/data')
getData(@ApiKey apiKey: string) {
return { apiKey };
}
}createCustomParamDecorator Options
| Property | Type | Description |
|---|---|---|
required | boolean= false | Throw MissingParameterError if the extracted value is undefined |
transform? | TransformFn | Transform the extracted value (sync or async) |
Custom parameter decorators use the 'custom' param source internally. They can only be used on
method parameters, not constructor parameters.
Guards
Guards control access to routes by returning a boolean. You can write guards as functions or as DI-injectable classes.
import type { GuardFn, GuardContext } from '@nextrush/decorators';
// Simple guard
const AuthGuard: GuardFn = async (ctx) => {
return Boolean(ctx.get('authorization'));
};
// Guard factory
const RoleGuard =
(roles: string[]): GuardFn =>
async (ctx) => {
const user = ctx.state.user as { role: string } | undefined;
return user ? roles.includes(user.role) : false;
};
@UseGuard(AuthGuard)
@UseGuard(RoleGuard(['admin']))
@Controller('/admin')
class AdminController {
@Get()
dashboard() {
return { admin: true };
}
}import type { CanActivate, GuardContext } from '@nextrush/decorators';
import { Service } from '@nextrush/di';
@Service()
class AuthGuard implements CanActivate {
async canActivate(ctx: GuardContext): Promise<boolean> {
const token = ctx.get('authorization');
if (!token) return false;
const user = await verifyToken(token);
ctx.state.user = user;
return Boolean(user);
}
}
// Use class guard - resolved from DI
@UseGuard(AuthGuard)
@Controller('/protected')
class ProtectedController {}Guard Execution Order
Guards run in order: class guards first, then method guards.
@UseGuard(ClassGuard1) // 1st
@UseGuard(ClassGuard2) // 2nd
@Controller('/example')
class ExampleController {
@UseGuard(MethodGuard1) // 3rd
@UseGuard(MethodGuard2) // 4th
@Get()
handler() {}
}Guard Context
Guards receive a lightweight context — no response methods, only request data:
GuardContext
| Property | Type | Description |
|---|---|---|
method | string | HTTP request method |
path | string | Request path |
params | Record<string, string> | Route parameters |
query | Record<string, string | string[] | undefined> | Query parameters |
headers | Record<string, string | string[] | undefined> | Request headers |
body | unknown | Parsed request body |
state | Record<string, unknown> | Mutable state bag shared between middleware and guards |
get(name) | (name: string) => string | undefined | Get a specific header value |
Metadata Readers
Access decorator metadata programmatically:
import {
getControllerMetadata,
getRouteMetadata,
getParamMetadata,
getAllGuards,
isController,
getResponseHeaders,
getRedirectMetadata,
} from '@nextrush/decorators';
// Check if class is a controller
isController(UserController); // true
// Get controller metadata
const meta = getControllerMetadata(UserController);
// { path: '/users', version: undefined, middleware: undefined, tags: undefined }
// Get all routes for a controller
const routes = getRouteMetadata(UserController);
// [{ method: 'GET', path: '/', ... }, { method: 'GET', path: '/:id', ... }]
// Get parameter metadata for a specific method
const params = getParamMetadata(UserController, 'findOne');
// [{ source: 'param', index: 0, name: 'id' }]
// Get all guards for a route (class + method guards)
const guards = getAllGuards(UserController, 'findOne');
// Get response headers for a method
const headers = getResponseHeaders(UserController.prototype, 'getData');
// [{ name: 'Cache-Control', value: 'no-store' }]
// Get redirect metadata for a method
const redirect = getRedirectMetadata(UserController.prototype, 'oldDashboard');
// { url: '/new-dashboard', statusCode: 301 }TypeScript
All types and runtime exports are available from the package entry point.
Complete import reference
import type {
ControllerMetadata,
ControllerOptions,
RouteMetadata,
RouteOptions,
ParamMetadata,
BodyOptions,
ParamOptions,
QueryOptions,
HeaderOptions,
GuardFn,
GuardContext,
GuardMetadata,
CanActivate,
Guard,
Constructor,
TransformFn,
ParamSource,
RouteMethods,
MiddlewareRef,
ControllerDefinition,
CustomParamExtractor,
ResponseHeaderMetadata,
RedirectMetadata,
} from '@nextrush/decorators';
import {
Controller,
Get,
Post,
Put,
Patch,
Delete,
Head,
Options,
All,
Body,
Param,
Query,
Header,
Ctx,
Req,
Res,
UseGuard,
SetHeader,
Redirect,
createCustomParamDecorator,
isController,
getControllerMetadata,
getRouteMetadata,
getParamMetadata,
getAllParamMetadata,
getControllerDefinition,
buildFullPath,
getAllGuards,
getClassGuards,
getMethodGuards,
isGuardClass,
getResponseHeaders,
getRedirectMetadata,
isValidHttpMethod,
isValidParamSource,
DECORATOR_METADATA_KEYS,
} from '@nextrush/decorators';Related
- DI Overview — DI & Decorators overview
- @nextrush/di — Dependency injection
- @nextrush/controllers — Controller plugin