Custom Parameter Decorators

Build reusable @CurrentUser()-style decorators with createParamDecorator, and compose them with pipes.

Reaching into request.user in every handler is repetitive and leaks framework details into your business code. A custom parameter decorator gives that value a clean, typed name.

createParamDecorator

The factory receives the decorator's argument (data) and the ExecutionContext, and returns whatever value should be injected into the parameter. The classic example pulls the authenticated user off the request:

export const CurrentUser = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) =>
    ctx.switchToHttp().getRequest().user,
);

// in a handler:
@Get('me')
me(@CurrentUser() user: User) { return user; }

Accepting an argument

The data parameter lets you build @CurrentUser('email') that returns a single field. Guard against a missing user so the decorator degrades gracefully.

Decorators compose with pipes

A parameter decorator can be combined with a pipe in the same slot: @Body(new ValidationPipe()) dto or @CurrentUser(ValidationPipe). This is powerful for extracting and validating in one place.

Works across transports

Because you go through ExecutionContext, the same decorator can branch on ctx.getType() to support HTTP, WebSockets, and microservice handlers - a single @CurrentUser() that works everywhere.

Example

Example Β· typescript
import {
  createParamDecorator,
  ExecutionContext,
  UnauthorizedException,
} from '@nestjs/common';

export interface AuthUser {
  id: string;
  email: string;
  roles: string[];
}

// @CurrentUser()          -> the whole user
// @CurrentUser('email')   -> just one field
export const CurrentUser = createParamDecorator(
  (field: keyof AuthUser | undefined, ctx: ExecutionContext): unknown => {
    // branch by transport so the decorator is universal
    const req =
      ctx.getType() === 'http'
        ? ctx.switchToHttp().getRequest()
        : ctx.switchToWs().getClient().handshake;

    const user = req.user as AuthUser | undefined;
    if (!user) return undefined; // let the guard decide auth
    return field ? user[field] : user;
  },
);

// A second decorator that pulls a validated pagination cursor.
export const Cursor = createParamDecorator(
  (_data: unknown, ctx: ExecutionContext) => {
    const q = ctx.switchToHttp().getRequest().query;
    return {
      limit: Math.min(Number(q.limit ?? 20), 100),
      after: (q.after as string) ?? null,
    };
  },
);

// Usage inside a controller:
// @Get('me')
// me(@CurrentUser() user: AuthUser) { return user; }
//
// @Get('feed')
// feed(@CurrentUser('id') userId: string, @Cursor() cursor) {
//   return this.feed.load(userId, cursor);
// }

When to use it

  • A developer creates a @CurrentUser() decorator so every protected handler reads the authenticated user from the request without calling req.user manually.
  • A team writes an @ActiveTenant() decorator that extracts the tenant ID from the JWT payload and pipes it through a validation pipe before it reaches the handler.
  • An engineer composes @CurrentUser() with ParseIntPipe to convert the user ID string from the JWT to a number in a single decorator usage.

More examples

@CurrentUser decorator

Creates a @CurrentUser() decorator using createParamDecorator that returns the entire user object or a specific field when a key is passed.

Example Β· ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    return data ? user?.[data] : user;  // @CurrentUser('id') or @CurrentUser()
  },
);

Using the custom decorator

Uses @CurrentUser() to inject the full user and @CurrentUser("id") to inject just the id field in two different routes.

Example Β· ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CurrentUser } from '../auth/current-user.decorator';

@Controller('me')
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@CurrentUser() user: UserPayload) {
    return user;  // full user object from JWT
  }

  @Get('id')
  getId(@CurrentUser('id') id: number) {
    return { id };  // just the id field
  }
}

Composing decorator with a pipe

Demonstrates composing a custom parameter decorator with a pipe by passing the pipe as an argument to the decorator.

Example Β· ts
import { createParamDecorator, ExecutionContext, ParseIntPipe } from '@nestjs/common';

export const UserId = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): string => {
    return ctx.switchToHttp().getRequest().user?.id;
  },
);

// Usage: coerce the string ID to number via pipe
@Get('profile')
getProfile(@UserId(new ParseIntPipe()) id: number) {
  return this.usersService.findOne(id);
}

Discussion

  • Be the first to comment on this lesson.