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