NestJS Enterprise Backend APIs · บทเรียน

การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector

กำหนดข้อมูลกำกับระดับเส้นทางและอ่านกลับภายในการ์ดและตัวดักรับผ่านบริการ Reflector

บทเรียน 2 จาก 413 ขั้นตอน

การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector เป็นบทเรียน NestJS Enterprise Backend APIs ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน NestJS Enterprise Backend APIs และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส NestJS Enterprise Backend APIs มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

Why Route Metadata?

In an enterprise NestJS API you often need to tag a route with extra information that guards, interceptors, or pipes can read later. Examples include required roles, permission flags, cache TTLs, or a flag that marks a route as public.

NestJS solves this with metadata reflection: you attach key/value data to a handler or controller class, then read it back at request time using the Reflector service. This keeps cross-cutting concerns out of your business logic.

  • SetMetadata — writes the metadata onto the route.
  • Reflector — reads it back inside guards/interceptors.

Attaching Metadata with SetMetadata

SetMetadata(key, value) is a decorator factory from @nestjs/common. You give it a string key and any value, and apply it to a controller method (or whole class).

Here we tag the findAll handler with a list of roles allowed to call it. The metadata is stored against the route handler but does nothing on its own until something reads it.

import { Controller, Get, SetMetadata } from '@nestjs/common';

@Controller('reports')
export class ReportsController {
  @Get()
  @SetMetadata('roles', ['admin', 'manager'])
  findAll() {
    return ['Q1 report', 'Q2 report'];
  }
}

Custom Decorators Wrap SetMetadata

Calling @SetMetadata('roles', [...]) inline everywhere is repetitive and easy to typo. The idiomatic pattern is to wrap it in a named custom decorator so the key lives in exactly one place.

Now controllers read clearly with @Roles('admin'), and the magic string 'roles' is encapsulated.

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

export const ROLES_KEY = 'roles';

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

Using the Custom Decorator

With the Roles decorator defined, controllers become declarative. The handler simply states who may access it; the enforcement logic lives elsewhere in a guard.

Exporting a shared ROLES_KEY constant is important: the decorator and the guard must agree on the exact key string, otherwise the read returns undefined.

import { Controller, Delete, Param } from '@nestjs/common';
import { Roles } from './roles.decorator';

@Controller('users')
export class UsersController {
  @Delete(':id')
  @Roles('admin')
  remove(@Param('id') id: string) {
    return `Deleted user ${id}`;
  }
}

Reading Metadata with Reflector

The Reflector service (from @nestjs/core) reads metadata back at runtime. Inject it into a guard, then call reflector.get(key, target) where target is usually the route handler returned by context.getHandler().

If no metadata was set, get returns undefined, so guards typically treat that as 'no restriction' and allow the request.

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

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

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>(
      ROLES_KEY,
      context.getHandler(),
    );
    if (!roles) return true; // no @Roles => open route

    const req = context.switchToHttp().getRequest();
    return roles.includes(req.user?.role);
  }
}

getAllAndOverride for Handler + Class

Metadata can sit on the handler or the whole controller class. To merge both correctly, use getAllAndOverride: it scans an ordered list of targets and returns the first defined value. Put the handler first so a method-level decorator overrides a class-level one.

Pass [context.getHandler(), context.getClass()] as the targets array.

const roles = this.reflector.getAllAndOverride<string[]>(
  ROLES_KEY,
  [context.getHandler(), context.getClass()],
);

// Handler-level @Roles wins over class-level @Roles

getAllAndMerge for Combining Values

Sometimes you don't want override semantics — you want to combine metadata from both handler and class. getAllAndMerge concatenates arrays (or merges objects) from every target.

  • getAllAndOverride → first defined value wins.
  • getAllAndMerge → all values combined into one array/object.

Use merge when permissions accumulate; use override when the most specific level should fully replace broader ones.

// Class: @Roles('staff')  Handler: @Roles('admin')
const merged = this.reflector.getAllAndMerge<string[]>(
  ROLES_KEY,
  [context.getHandler(), context.getClass()],
);
// merged => ['admin', 'staff']

A Public Route Flag

A very common enterprise pattern is a global JwtAuthGuard that protects every route, plus an @Public() escape hatch for login or health-check endpoints. The decorator just sets a boolean flag.

The global guard reads that flag first and skips authentication when it is true.

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

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

Honoring the Public Flag in a Guard

Inside the global auth guard, read IS_PUBLIC_KEY with getAllAndOverride across handler and class. If it is true, short-circuit and allow the request before any JWT validation runs.

This is why metadata reflection is powerful: one decorator declaratively changes how an unrelated guard behaves.

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

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(
      IS_PUBLIC_KEY,
      [context.getHandler(), context.getClass()],
    );
    if (isPublic) return true;
    return validateJwt(context); // your real check
  }
}

Reflector in Interceptors Too

Guards aren't the only consumers. Interceptors also receive an ExecutionContext, so they can read metadata the same way. A classic example is a per-route cache TTL.

Define a @CacheTtl(60) decorator with SetMetadata('cacheTtl', 60), then read it inside an interceptor to decide caching behavior dynamically.

@Injectable()
export class TtlInterceptor implements NestInterceptor {
  constructor(private reflector: Reflector) {}

  intercept(context: ExecutionContext, next: CallHandler) {
    const ttl = this.reflector.get<number>(
      'cacheTtl',
      context.getHandler(),
    ) ?? 30;
    console.log(`Caching for ${ttl}s`);
    return next.handle();
  }
}

Plain TypeScript: The Core Idea

Under the hood, Nest's reflection is just storing values in a map keyed by the function and a metadata key. Here is the same idea in plain, runnable TypeScript with no framework: a tiny store, a 'decorator' that writes, and a 'reflector' that reads with override semantics.

type Target = Function;
const store = new Map<Target, Map<string, unknown>>();

function setMeta(key: string, value: unknown, t: Target) {
  if (!store.has(t)) store.set(t, new Map());
  store.get(t)!.set(key, value);
}

function getAllAndOverride<T>(key: string, targets: Target[]): T | undefined {
  for (const t of targets) {
    const v = store.get(t)?.get(key);
    if (v !== undefined) return v as T;
  }
  return undefined;
}

function handler() {}
function controller() {}

setMeta('roles', ['staff'], controller);
setMeta('roles', ['admin'], handler);

const roles = getAllAndOverride<string[]>('roles', [handler, controller]);
console.log('Effective roles:', roles); // ['admin'] — handler wins

Quick Check

You apply @Roles('staff') on the controller class and @Roles('admin') on a specific handler. Inside the guard you want the handler-level value to fully replace the class-level value. Which Reflector call should you use?

Recap

You learned how to attach and read route-level metadata in NestJS:

  • SetMetadata(key, value) attaches data to a handler or class; wrap it in a named custom decorator (e.g. @Roles(), @Public()) and share the key via a constant.
  • Reflector.get reads metadata from a single target like context.getHandler().
  • getAllAndOverride scans [getHandler(), getClass()] and returns the first defined value — handler overrides class.
  • getAllAndMerge combines values from all targets instead of overriding.
  • Both guards and interceptors consume metadata the same way, enabling clean cross-cutting concerns like roles, public routes, and cache TTLs.
เริ่มต้นได้ฟรี

เรียนรู้ TypeScript ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
20
บทเรียน
76

คำถามที่พบบ่อย

บทเรียน “การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส NestJS Enterprise Backend APIs ให้อัปเกรดเป็น CoddyKit PRO คอร์ส NestJS Enterprise Backend APIs มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector”

กำหนดข้อมูลกำกับระดับเส้นทางและอ่านกลับภายในการ์ดและตัวดักรับผ่านบริการ Reflector คุณปฏิบัติ NestJS Enterprise Backend APIs ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน NestJS Enterprise Backend APIs หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน NestJS Enterprise Backend APIs บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน NestJS Enterprise Backend APIs นี้ได้ไหม

ได้ บทเรียน NestJS Enterprise Backend APIs ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การอ่านบริบทคำขอด้วยตัวตกแต่งพารามิเตอร์
  2. การแนบข้อมูลกำกับด้วย SetMetadata และ Reflector
  3. การประกอบตัวตกแต่งด้วย applyDecorators
  4. ตัวตกแต่งระดับคลาสสำหรับการกำหนดค่าร่วม
← กลับไปที่ NestJS Enterprise Backend APIs