ExecutionContext & Reflector

Read Metadata Set by Decorators

ExecutionContext & Reflector

ExecutionContext is the "where am I?" object guards and interceptors receive. Reflector reads custom metadata attached by decorators.

4 min read Level 3/5 #nestjs#advanced#metadata
What you'll learn
  • Get the request, handler, and class from ExecutionContext
  • Read metadata with Reflector.get and getAllAndOverride
  • Set custom metadata with SetMetadata or a typed factory

Guards, interceptors, and filters all receive an ExecutionContext. It’s how they ask Nest: which handler is this? which controller? what transport — HTTP, RPC, WebSocket? Combined with Reflector, it powers every metadata-driven pattern in Nest, including the classic roles guard.

What ExecutionContext Gives You

canActivate(ctx: ExecutionContext) {
  // Transport-specific accessors
  const req = ctx.switchToHttp().getRequest();
  const res = ctx.switchToHttp().getResponse();

  // Who is being called
  const handler = ctx.getHandler();  // the method (a Function)
  const controller = ctx.getClass(); // the controller class
  const args = ctx.getArgs();        // raw transport args

  return true;
}

switchToHttp() is the right entry for REST. The same context object also has switchToRpc() and switchToWs() so the same guard can serve microservices or sockets.

Setting Custom Metadata

Use SetMetadata(key, value) — or, better, wrap it in a typed decorator.

import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

Now any route can declare its required roles:

@Delete(':id')
@Roles('admin')
remove(@Param('id') id: string) {
  return this.users.remove(id);
}

Reading It With Reflector

Inject Reflector and ask for the key. The handy getAllAndOverride checks the method first, then falls back to the controller — exactly what you want for “method overrides class” semantics.

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(ctx: ExecutionContext): boolean {
    const required = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      ctx.getHandler(),
      ctx.getClass(),
    ]);
    if (!required?.length) return true;            // no decorator -> open

    const { user } = ctx.switchToHttp().getRequest();
    return required.some((role) => user?.roles?.includes(role));
  }
}

Apply the guard globally and routes opt in to protection just by adding @Roles(...).

The Pattern in General

This decorator + reflector + guard triangle is how Nest builds declarative features:

  1. A decorator (@Roles, @Public, @RateLimit) attaches metadata.
  2. A guard or interceptor reads it from ExecutionContext via Reflector.
  3. The handler stays clean — the rule lives next to the route definition.

Most third-party Nest libraries (passport, throttler, swagger) work the same way. Once you’ve written one, you’ll spot the pattern everywhere.

ExecutionContext vs ArgumentsHost

A small naming note: exception filters receive ArgumentsHost instead of ExecutionContext. ExecutionContext extends ArgumentsHost with getHandler and getClass, which filters don’t always have access to. Otherwise they behave identically.

This wraps up the pipeline section. With pipes, guards, interceptors, filters, and metadata, you’ve got every Nest tool for shaping the request lifecycle.

Persistence With TypeORM →