Careeroza
Param Decorators
@CurrentUser
After authentication, Passport puts the user on request.user. Instead of @Req() req everywhere, create a param decorator.
src/auth/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface JwtUser { id: number; email: string; roles: string[] }
export const CurrentUser = createParamDecorator(
(field: keyof JwtUser | undefined, ctx: ExecutionContext) => {
const user = ctx.switchToHttp().getRequest().user as JwtUser | undefined;
return field ? user?.[field] : user;
},
);
// usage
// @Get('me') me(@CurrentUser() user: JwtUser) { return user; }
// @Get('me/orders') myOrders(@CurrentUser('id') userId: number) { ... }Tip: Param decorators work with pipes too: @CurrentUser('id', ParseIntPipe) id: number. To make custom decorators run through the global ValidationPipe, set validateCustomDecorators: true.
How Param Decorators Work
Example
A param decorator records "for argument N, run this extractor". At request time the framework builds the argument list from the request. A runnable version with legacy parameter decorators:
type Req = { user?: { id: number; email: string }; params: Record<string, string>; headers: Record<string, string> };
type Extractor = (req: Req) => unknown;
const argMeta = new Map<string, Extractor[]>();
function createParamDecorator<D>(fn: (data: D, req: Req) => unknown) {
return (data: D): ParameterDecorator => (target, key, index) => {
const id = (target.constructor as Function).name + '.' + String(key);
const list = argMeta.get(id) ?? [];
list[index] = (req) => fn(data, req);
argMeta.set(id, list);
};
}
const CurrentUser = createParamDecorator((field: 'id' | 'email' | undefined, req) => (field ? req.user?.[field] : req.user));
const Param = createParamDecorator((name: string, req) => req.params[name]);
const Header = createParamDecorator((name: string, req) => req.headers[name]);
class OrdersController {
findOne(@CurrentUser('id') userId: number, @Param('orderId') orderId: string, @Header('x-app') app: string) {
return 'user ' + userId + ' reads order ' + orderId + ' from ' + app;
}
}
function invoke(ctrl: OrdersController, method: 'findOne', req: Req) {
const extractors = argMeta.get('OrdersController.' + method) ?? [];
const args = extractors.map((ex) => ex(req));
console.log('resolved args:', args);
return (ctrl[method] as (...a: unknown[]) => string)(...args);
}
console.log(invoke(new OrdersController(), 'findOne', {
user: { id: 12, email: '[email protected]' },
params: { orderId: 'ORD-7781' },
headers: { 'x-app': 'android' },
}));Output
resolved args: [ 12, 'ORD-7781', 'android' ] user 12 reads order ORD-7781 from android