[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-nestjs-weekly-guards-canactivate-reflector":44,"search-suggestions":60,"quiz-article-nestjs-weekly-guards-canactivate-reflector":107},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a0ddae-48fd-75e3-89c9-d4789d07f556","nestjs-weekly-guards-canactivate-reflector","PRACTICE_QUIZ","NestJS Guards: CanActivate, ExecutionContext & Reflector","Test what you learned about how NestJS guards decide who reaches a route handler — CanActivate's contract, ExecutionContext, Reflector metadata resolution, and guard registration.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},7,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,97,100,104],{"slug":62,"name":63,"articles":64},"webdev","Webdev",111,{"slug":66,"name":67,"articles":68},"javascript","Javascript",93,{"slug":70,"name":71,"articles":72},"frontend","Frontend",73,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",39,{"slug":78,"name":79,"articles":80},"css","Css",36,{"slug":82,"name":83,"articles":84},"typescript","Typescript",16,{"slug":86,"name":87,"articles":88},"performance","Performance",14,{"slug":90,"name":91,"articles":92},"react","React",13,{"slug":94,"name":95,"articles":96},"browser","Browser",10,{"slug":98,"name":99,"articles":96},"node","Node",{"slug":101,"name":102,"articles":103},"html","Html",8,{"slug":105,"name":106,"articles":51},"accessibility","Accessibility",{"id":108,"slug":46,"title":48,"subtitle":52,"excerpt":109,"coverUrl":110,"locale":13,"readingMinutes":88,"publishedAt":111,"viewCount":112,"likeCount":19,"commentCount":19,"author":113,"vertical":118,"topic":119,"tags":122,"_count":127,"playground":129,"body":131,"bodyMd":478,"seo":479,"translationGroupId":481,"series":482,"podcastUrl":52,"verticalId":5,"thread":503,"assessments":505,"translations":508,"quiz":510},"01a0ddae-48ac-73c6-bdee-1420cdfb17c2","A complete guide to NestJS guards: how CanActivate, ExecutionContext, and the Reflector decide who reaches your route handler, with a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-guards-canactivate-reflector.png","2026-09-26T12:25:39.001Z",47,{"id":114,"name":115,"username":116,"avatarUrl":52,"headline":117},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":120,"name":121},"nestjs","Nestjs",[123,124,125,126],{"slug":120,"name":121,"color":52},{"slug":98,"name":99,"color":52},{"slug":82,"name":83,"color":52},{"slug":74,"name":75,"color":52},{"assessments":128},1,{"slug":46,"title":130},"NestJS guards — CanActivate & Reflector playground",{"blocks":132,"version":128},[133,137,140,143,148,151,160,163,166,169,184,188,191,197,200,204,207,210,213,216,219,225,228,231,235,238,242,245,248,251,254,257,261,264,268,271,275,279,283,286,289,292,296,299,302,306,309,312,316,319,322,330,333,341,344,347,350,353,356,360,363,367,370,374,377,380,384,410,413,416,424,427,430,433,436,439,442,445,448,451,454,457,460,463,466,469,472],{"id":134,"html":135,"type":136},"b1","\u003Cp>A code review flags a &quot;protected&quot; admin endpoint. The controller has \u003Ccode>@UseGuards(RolesGuard)\u003C\u002Fcode> on the class, \u003Ccode>RolesGuard\u003C\u002Fcode> injects a \u003Ccode>UserService\u003C\u002Fcode> to check role hierarchy, and it&#39;s registered application-wide with \u003Ccode>app.useGlobalGuards(new RolesGuard(reflector, userService))\u003C\u002Fcode> in \u003Ccode>main.ts\u003C\u002Fcode>. It looks careful. It is also broken: \u003Ccode>userService\u003C\u002Fcode> is \u003Ccode>undefined\u003C\u002Fcode> inside the guard, on every single request, and no test would have caught it because the app never crashed — it just let every request through the branch that assumes a user has no elevated role.\u003C\u002Fp>","paragraph",{"id":138,"html":139,"type":136},"b2","\u003Cp>Nothing here is a typo. The guard class is correct. The decorator is correct. The one thing wrong is \u003Cem>how\u003C\u002Fem> the guard was registered — and until you know what a guard actually is to Nest&#39;s container, that line looks completely reasonable.\u003C\u002Fp>",{"id":141,"html":142,"type":136},"b3","\u003Cp>This is written against \u003Cstrong>NestJS 12.0.x\u003C\u002Fstrong> (verified September 2026, current \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> release, \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\">v12.0.4\u003C\u002Fa>). The \u003Ccode>CanActivate\u003C\u002Fcode> interface, \u003Ccode>ExecutionContext\u003C\u002Fcode>, and \u003Ccode>Reflector\u003C\u002Fcode> covered here have been stable since well before v9 and are untouched by v12&#39;s ESM and Standard Schema changes — nothing in this article is version-fragile.\u003C\u002Fp>",{"id":144,"html":145,"text":146,"type":147,"level":31},"b4","What you&#39;ll learn","What you'll learn","heading",{"id":149,"html":150,"type":136},"b5","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":152,"type":153,"items":154,"ordered":18},"b6","list",[155,156,157,158,159],"Explain what a guard actually is — a DI-instantiated class, not a function you happen to call \u003Ccode>canActivate\u003C\u002Fcode>","Read and use \u003Ccode>ExecutionContext\u003C\u002Fcode> to get at the request, the handler, and the controller class from inside a guard","Build a \u003Ccode>Reflector\u003C\u002Fcode>-backed \u003Ccode>@Roles()\u003C\u002Fcode> decorator and understand exactly why the method&#39;s metadata overrides the class&#39;s","Register a global guard the \u003Cem>right\u003C\u002Fem> way (\u003Ccode>APP_GUARD\u003C\u002Fcode>) so it keeps its dependency injection","Predict, for any combination of global\u002Fcontroller\u002Fmethod guards, the exact order they run in and what &quot;all must pass&quot; means",{"id":161,"html":162,"text":162,"type":147,"level":31},"b7","Who this is for",{"id":164,"html":165,"type":136},"b8","\u003Cp>You&#39;ve written a NestJS controller, used \u003Ccode>@UseGuards()\u003C\u002Fcode> at least once, and you know what \u003Ccode>@Injectable()\u003C\u002Fcode> does. If you haven&#39;t read the \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-request-lifecycle-explained-with-cheat-sheet-227l\">NestJS request lifecycle\u003C\u002Fa> episode of this series, it&#39;s a useful map of where guards sit relative to middleware, interceptors, and pipes — but this article is self-contained.\u003C\u002Fp>",{"id":167,"html":168,"text":168,"type":147,"level":31},"b9","Table of contents",{"id":170,"type":153,"items":171,"ordered":18},"b10",[172,173,174,175,176,177,178,179,180,181,182,183],"\u003Ca href=\"#the-problem-a-guard-that-quietly-cant-do-its-job\">The problem: a guard that quietly can&#39;t do its job\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-a-guard-is-a-provider-not-a-function\">The mental model: a guard is a provider, not a function\u003C\u002Fa>","\u003Ca href=\"#stage-1-the-smallest-correct-guard\">Stage 1: the smallest correct guard\u003C\u002Fa>","\u003Ca href=\"#stage-2-executioncontext-properly\">Stage 2: ExecutionContext, properly\u003C\u002Fa>","\u003Ca href=\"#stage-3-reflector-and-a-real-roles-decorator\">Stage 3: Reflector and a real \u003Ccode>@Roles()\u003C\u002Fcode> decorator\u003C\u002Fa>","\u003Ca href=\"#stage-4-registering-a-global-guard-without-losing-di\">Stage 4: registering a global guard without losing DI\u003C\u002Fa>","\u003Ca href=\"#stage-5-composing-guards--order-and-all-must-pass\">Stage 5: composing guards — order and &quot;all must pass&quot;\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":185,"html":186,"text":187,"type":147,"level":31},"b11","The problem: a guard that quietly can&#39;t do its job","The problem: a guard that quietly can't do its job",{"id":189,"html":190,"type":136},"b12","\u003Cp>Here&#39;s the guard from the intro, in full:\u003C\u002Fp>",{"id":192,"code":193,"type":194,"language":195,"highlight":196},"b13","@Injectable()\nexport class RolesGuard implements CanActivate {\n  constructor(\n    private reflector: Reflector,\n    private userService: UserService, \u002F\u002F meant to check role hierarchy\n  ) {}\n\n  canActivate(context: ExecutionContext): boolean {\n    const required = this.reflector.get\u003Cstring[]>(\"roles\", context.getHandler());\n    if (!required) return true;\n    const { user } = context.switchToHttp().getRequest();\n    \u002F\u002F userService is undefined here — this branch always throws or always no-ops\n    return this.userService.hasAnyRole(user, required);\n  }\n}","code","ts",[],{"id":198,"html":199,"type":136},"b14","\u003Cp>And here&#39;s how it was wired up, because &quot;global&quot; sounded like the right word for &quot;runs on every route&quot;:\u003C\u002Fp>",{"id":201,"code":202,"type":194,"language":195,"highlight":203},"b15","\u002F\u002F main.ts — looks reasonable, is quietly broken\nconst app = await NestFactory.create(AppModule);\napp.useGlobalGuards(new RolesGuard(new Reflector(), \u002F* userService? *\u002F undefined));",[],{"id":205,"html":206,"type":136},"b16","\u003Cp>\u003Ccode>app.useGlobalGuards()\u003C\u002Fcode> takes an \u003Cem>instance\u003C\u002Fem>. Nothing about that instance goes through Nest&#39;s dependency injection — you constructed it yourself, with \u003Ccode>new\u003C\u002Fcode>, outside any module, before the application&#39;s providers even exist to hand it a real \u003Ccode>UserService\u003C\u002Fcode>. Nest will happily call \u003Ccode>canActivate()\u003C\u002Fcode> on this object for every request. It just can&#39;t give it the dependency the guard was written to use.\u003C\u002Fp>",{"id":208,"html":209,"type":136},"b17","\u003Cp>The fix isn&#39;t a different guard. It&#39;s a different \u003Cem>registration\u003C\u002Fem>, covered in \u003Ca href=\"#stage-4-registering-a-global-guard-without-losing-di\">Stage 4\u003C\u002Fa>. But to see why that fix works, you need the actual mental model of what a guard is.\u003C\u002Fp>",{"id":211,"html":212,"text":212,"type":147,"level":31},"b18","The mental model: a guard is a provider, not a function",{"id":214,"html":215,"type":136},"b19","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> a NestJS guard is an ordinary DI provider — the same kind of class as a service — that additionally implements one method, \u003Ccode>canActivate(context: ExecutionContext)\u003C\u002Fcode>, which Nest calls immediately before it would otherwise invoke your route handler. If \u003Ccode>canActivate\u003C\u002Fcode> resolves to \u003Ccode>true\u003C\u002Fcode>, the request keeps moving toward interceptors, pipes, and the handler. If it resolves to \u003Ccode>false\u003C\u002Fcode>, or throws, the handler never runs and Nest hands the request straight to the exception-filter layer.\u003C\u002Fp>",{"id":217,"html":218,"type":136},"b20","\u003Cp>Three consequences fall out of that one sentence, and they&#39;re the three things the intro&#39;s bug got wrong:\u003C\u002Fp>",{"id":220,"type":153,"items":221,"ordered":17},"b21",[222,223,224],"\u003Cstrong>A guard only gets real dependency injection if Nest constructs it.\u003C\u002Fstrong> \u003Ccode>@UseGuards(RolesGuard)\u003C\u002Fcode> (passing the \u003Cem>class\u003C\u002Fem>) lets Nest instantiate it through the container, resolving its constructor arguments normally. \u003Ccode>new RolesGuard(...)\u003C\u002Fcode> (passing an \u003Cem>instance\u003C\u002Fem> you built) does not — you&#39;re on your own for every dependency.","\u003Cstrong>A guard doesn&#39;t see &quot;a request&quot; in the abstract — it sees an \u003Ccode>ExecutionContext\u003C\u002Fcode>.\u003C\u002Fstrong> That&#39;s a wrapper Nest builds fresh for every incoming call, giving the guard a uniform way to reach the underlying request \u003Cem>and\u003C\u002Fem> to ask &quot;what handler and class is Nest about to invoke?&quot; — which is exactly what a guard needs to look up route-specific metadata.","\u003Cstrong>A guard&#39;s decision is binary, not additive.\u003C\u002Fstrong> It either lets a request through or it doesn&#39;t. Anything more nuanced than &quot;yes\u002Fno&quot; — attaching data, transforming the body — is an interceptor&#39;s or a pipe&#39;s job, not a guard&#39;s.",{"id":226,"html":227,"text":227,"type":147,"level":31},"b22","Stage 1: the smallest correct guard",{"id":229,"html":230,"type":136},"b23","\u003Cp>The simplest guard that compiles and does something real:\u003C\u002Fp>",{"id":232,"code":233,"type":194,"language":195,"highlight":234},"b24","import { CanActivate, ExecutionContext, Injectable } from \"@nestjs\u002Fcommon\";\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n  canActivate(context: ExecutionContext): boolean {\n    const request = context.switchToHttp().getRequest();\n    return Boolean(request.headers.authorization);\n  }\n}",[],{"id":236,"html":237,"type":136},"b25","\u003Cp>Apply it to one route:\u003C\u002Fp>",{"id":239,"code":240,"type":194,"language":195,"highlight":241},"b26","@UseGuards(AuthGuard)\n@Get(\"profile\")\ngetProfile(@Req() req: Request) {\n  return req.user;\n}",[],{"id":243,"html":244,"type":136},"b27","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>canActivate\u003C\u002Fcode> can return \u003Ccode>boolean\u003C\u002Fcode>, \u003Ccode>Promise&lt;boolean&gt;\u003C\u002Fcode>, or \u003Ccode>Observable&lt;boolean&gt;\u003C\u002Fcode> — Nest awaits or subscribes to whichever you give it. A guard that calls a database or an external identity provider to check a session is completely normal; just make it \u003Ccode>async canActivate(...): Promise&lt;boolean&gt;\u003C\u002Fcode> and Nest will wait for it before deciding.\u003C\u002Fp>",{"id":246,"html":247,"text":247,"type":147,"level":31},"b28","Stage 2: ExecutionContext, properly",{"id":249,"html":250,"type":136},"b29","\u003Cp>\u003Ccode>ExecutionContext\u003C\u002Fcode> is the single argument every guard, interceptor, and exception filter receives, and it answers two different questions:\u003C\u002Fp>",{"id":252,"html":253,"type":136},"b30","\u003Cp>\u003Cstrong>&quot;What kind of call is this, and what&#39;s the underlying request object?&quot;\u003C\u002Fstrong> — via \u003Ccode>switchToHttp()\u003C\u002Fcode>, \u003Ccode>switchToRpc()\u003C\u002Fcode>, or \u003Ccode>switchToWs()\u003C\u002Fcode>. Most guards only ever call \u003Ccode>context.switchToHttp().getRequest()\u003C\u002Fcode>, but the same guard class \u003Cem>can\u003C\u002Fem> run in front of a WebSocket gateway or a microservice handler if you check \u003Ccode>context.getType()\u003C\u002Fcode> first and branch — that&#39;s what makes \u003Ccode>ExecutionContext\u003C\u002Fcode> a context, not just an HTTP request wrapper.\u003C\u002Fp>",{"id":255,"html":256,"type":136},"b31","\u003Cp>\u003Cstrong>&quot;Which handler and which class is Nest about to call?&quot;\u003C\u002Fstrong> — via \u003Ccode>context.getHandler()\u003C\u002Fcode> (the specific route method, as a function reference) and \u003Ccode>context.getClass()\u003C\u002Fcode> (the controller class). This half is what makes metadata-driven guards possible, because it&#39;s the \u003Cem>only\u003C\u002Fem> way a guard can ask &quot;does this specific route carry a \u003Ccode>@Roles(...)\u003C\u002Fcode> decorator?&quot; — the guard runs once per registration, but \u003Ccode>getHandler()\u003C\u002Fcode>\u002F\u003Ccode>getClass()\u003C\u002Fcode> tell it which route it&#39;s currently deciding for.\u003C\u002Fp>",{"id":258,"code":259,"type":194,"language":195,"highlight":260},"b32","canActivate(context: ExecutionContext): boolean {\n  const handlerName = context.getHandler().name;      \u002F\u002F e.g. \"getProfile\"\n  const controllerName = context.getClass().name;     \u002F\u002F e.g. \"UsersController\"\n  const request = context.switchToHttp().getRequest();\n  \u002F\u002F ...\n}",[],{"id":262,"html":263,"type":136},"b33","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>getHandler()\u003C\u002Fcode> and \u003Ccode>getClass()\u003C\u002Fcode> return the raw function\u002Fclass references, not strings — they exist so you can hand them to \u003Ccode>Reflector\u003C\u002Fcode>, which looks up metadata \u003Cem>attached to those exact references\u003C\u002Fem>. That&#39;s the bridge to Stage 3.\u003C\u002Fp>",{"id":265,"html":266,"text":267,"type":147,"level":31},"b34","Stage 3: Reflector and a real \u003Ccode>@Roles()\u003C\u002Fcode> decorator","Stage 3: Reflector and a real @Roles() decorator",{"id":269,"html":270,"type":136},"b35","\u003Cp>Hardcoding a role check per route doesn&#39;t scale, and neither does branching on \u003Ccode>handler.name\u003C\u002Fcode> — a rename breaks it silently. The idiomatic pattern is a custom decorator that attaches metadata, and a \u003Ccode>Reflector\u003C\u002Fcode> that reads it back inside a guard.\u003C\u002Fp>",{"id":272,"code":273,"type":194,"language":195,"highlight":274},"b36","\u002F\u002F roles.decorator.ts\nimport { SetMetadata } from \"@nestjs\u002Fcommon\";\n\nexport const ROLES_KEY = \"roles\";\nexport const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);",[],{"id":276,"code":277,"type":194,"language":195,"highlight":278},"b37","\u002F\u002F roles.guard.ts\nimport { CanActivate, ExecutionContext, Injectable } from \"@nestjs\u002Fcommon\";\nimport { Reflector } from \"@nestjs\u002Fcore\";\nimport { ROLES_KEY } from \".\u002Froles.decorator\";\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n  constructor(private reflector: Reflector) {}\n\n  canActivate(context: ExecutionContext): boolean {\n    const required = this.reflector.getAllAndOverride\u003Cstring[]>(ROLES_KEY, [\n      context.getHandler(),\n      context.getClass(),\n    ]);\n    if (!required || required.length === 0) return true; \u002F\u002F no @Roles() = open route\n\n    const { user } = context.switchToHttp().getRequest();\n    return required.some((role) => user?.roles?.includes(role));\n  }\n}",[],{"id":280,"code":281,"type":194,"language":195,"highlight":282},"b38","@Controller(\"orders\")\n@Roles(\"editor\") \u002F\u002F class-level default\nexport class OrdersController {\n  @Get()\n  list() { \u002F* requires \"editor\" — inherits the class default *\u002F }\n\n  @Delete(\":id\")\n  @Roles(\"admin\") \u002F\u002F method-level override\n  remove() { \u002F* requires \"admin\" — this wins over the class's \"editor\" *\u002F }\n}",[],{"id":284,"html":285,"type":136},"b39","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>getAllAndOverride(key, [handler, class])\u003C\u002Fcode> checks the handler first, then the class, and returns the \u003Cstrong>first one it finds\u003C\u002Fstrong> — it does not merge arrays. That order in the array is why a method-level \u003Ccode>@Roles(&quot;admin&quot;)\u003C\u002Fcode> completely replaces the class-level \u003Ccode>@Roles(&quot;editor&quot;)\u003C\u002Fcode> rather than requiring both. If you actually want both handler and class metadata combined, \u003Ccode>Reflector\u003C\u002Fcode> also has \u003Ccode>getAllAndMerge()\u003C\u002Fcode>, which concatenates arrays instead of short-circuiting — reach for it explicitly when &quot;either level can add a role&quot; is the behavior you want.\u003C\u002Fp>",{"id":287,"html":288,"text":288,"type":147,"level":31},"b40","Stage 4: registering a global guard without losing DI",{"id":290,"html":291,"type":136},"b41","\u003Cp>Back to the intro&#39;s bug. \u003Ccode>app.useGlobalGuards(new RolesGuard(...))\u003C\u002Fcode> builds the guard outside the container, so any constructor dependency has to be supplied by hand — which is exactly what went wrong. The fix is to register the guard as a provider, using the \u003Ccode>APP_GUARD\u003C\u002Fcode> injection token from \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode>:\u003C\u002Fp>",{"id":293,"code":294,"type":194,"language":195,"highlight":295},"b42","import { APP_GUARD } from \"@nestjs\u002Fcore\";\n\n@Module({\n  providers: [\n    RolesGuard,\n    { provide: APP_GUARD, useClass: RolesGuard },\n  ],\n})\nexport class AppModule {}",[],{"id":297,"html":298,"type":136},"b43","\u003Cp>Because \u003Ccode>RolesGuard\u003C\u002Fcode> is now a normal provider, Nest resolves its constructor the usual way — \u003Ccode>Reflector\u003C\u002Fcode> (and, in the intro&#39;s case, a real \u003Ccode>UserService\u003C\u002Fcode>) get injected correctly, module-scoped providers work, and \u003Ccode>Test.createTestingModule\u003C\u002Fcode> can \u003Ccode>overrideProvider(RolesGuard)\u003C\u002Fcode> in tests the same way it overrides any other dependency, as covered in this series&#39; \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-testing-module-provider-overrides-with-cheat-sheet-1o79\">testing module episode\u003C\u002Fa>. None of that is available to a guard built with \u003Ccode>new\u003C\u002Fcode> in \u003Ccode>main.ts\u003C\u002Fcode>.\u003C\u002Fp>",{"id":300,"html":301,"type":136},"b44","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>app.useGlobalGuards()\u003C\u002Fcode> still exists and still works for a guard with zero dependencies — it&#39;s not deprecated. The rule is narrower and easy to remember: the moment a guard&#39;s constructor needs anything Nest would normally inject, register it through \u003Ccode>APP_GUARD\u003C\u002Fcode>, not \u003Ccode>useGlobalGuards()\u003C\u002Fcode>.\u003C\u002Fp>",{"id":303,"html":304,"text":305,"type":147,"level":31},"b45","Stage 5: composing guards — order and &quot;all must pass&quot;","Stage 5: composing guards — order and \"all must pass\"",{"id":307,"html":308,"type":136},"b46","\u003Cp>A single request can pass through guards registered at three different scopes at once: global (\u003Ccode>APP_GUARD\u003C\u002Fcode>, or \u003Ccode>useGlobalGuards()\u003C\u002Fcode>), controller (\u003Ccode>@UseGuards()\u003C\u002Fcode> on the class), and method (\u003Ccode>@UseGuards()\u003C\u002Fcode> on the handler). Nest runs them in that exact order — global, then controller, then method — and within one \u003Ccode>@UseGuards(A, B)\u003C\u002Fcode> call, in the order listed.\u003C\u002Fp>",{"id":310,"html":311,"type":136},"b47","\u003Cp>This composition is a logical \u003Cstrong>AND\u003C\u002Fstrong>, not a fallback chain: every guard in the sequence must return (or resolve to) \u003Ccode>true\u003C\u002Fcode>, or the request is rejected at the first one that doesn&#39;t. There&#39;s no &quot;guard B can override guard A&#39;s denial&quot; — a single \u003Ccode>false\u003C\u002Fcode> anywhere in the chain ends the request immediately, and every guard after it, plus every interceptor and pipe, is skipped entirely.\u003C\u002Fp>",{"id":313,"code":314,"type":194,"language":195,"highlight":315},"b48","@UseGuards(ThrottleGuard, RolesGuard) \u002F\u002F ThrottleGuard runs first\n@Controller(\"orders\")\nexport class OrdersController {}",[],{"id":317,"html":318,"type":136},"b49","\u003Cp>If a global \u003Ccode>AuthGuard\u003C\u002Fcode> is also registered via \u003Ccode>APP_GUARD\u003C\u002Fcode>, the real order for any route on this controller is: \u003Ccode>AuthGuard\u003C\u002Fcode> → \u003Ccode>ThrottleGuard\u003C\u002Fcode> → \u003Ccode>RolesGuard\u003C\u002Fcode> → (method-level guards, if any) → interceptors → pipes → the handler.\u003C\u002Fp>",{"id":320,"html":321,"text":321,"type":147,"level":31},"b50","Edge cases and gotchas",{"id":323,"type":153,"items":324,"ordered":18},"b51",[325,326,327,328,329],"\u003Cstrong>A guard that throws vs. a guard that returns \u003Ccode>false\u003C\u002Fcode>.\u003C\u002Fstrong> Returning \u003Ccode>false\u003C\u002Fcode> produces a generic \u003Ccode>403 Forbidden\u003C\u002Fcode>. Throwing a specific exception — \u003Ccode>throw new UnauthorizedException(&quot;Session expired&quot;)\u003C\u002Fcode> — gives the client (and your logs) a far more useful signal, and is the idiomatic choice for anything beyond &quot;just deny it.&quot;","\u003Cstrong>\u003Ccode>Reflector\u003C\u002Fcode> needs the exact same metadata key everywhere.\u003C\u002Fstrong> \u003Ccode>SetMetadata(ROLES_KEY, ...)\u003C\u002Fcode> and \u003Ccode>reflector.getAllAndOverride(ROLES_KEY, ...)\u003C\u002Fcode> must use the identical string (or, better, the same exported constant). A typo in one spot means the guard silently sees \u003Ccode>undefined\u003C\u002Fcode> and treats the route as unrestricted — this fails open, which is the worst direction for an auth check to fail.","\u003Cstrong>\u003Ccode>getAllAndOverride\u003C\u002Fcode> vs \u003Ccode>getAllAndMerge\u003C\u002Fcode>.\u003C\u002Fstrong> Covered in Stage 3, but worth repeating because it&#39;s the single most common \u003Ccode>Reflector\u003C\u002Fcode> mistake: reach for \u003Ccode>getAllAndOverride\u003C\u002Fcode> when a method should be able to fully replace a class default, and \u003Ccode>getAllAndMerge\u003C\u002Fcode> when both levels should contribute.","\u003Cstrong>WebSocket and microservice guards need a type check.\u003C\u002Fstrong> \u003Ccode>context.switchToHttp()\u003C\u002Fcode> throws if the current call isn&#39;t actually HTTP. A guard meant to run across transports should branch on \u003Ccode>context.getType()\u003C\u002Fcode> (\u003Ccode>&quot;http&quot;\u003C\u002Fcode>, \u003Ccode>&quot;ws&quot;\u003C\u002Fcode>, \u003Ccode>&quot;rpc&quot;\u003C\u002Fcode>) before picking which \u003Ccode>switchTo*()\u003C\u002Fcode> to call.","\u003Cstrong>A denied guard skips the &quot;before&quot; half of interceptors too.\u003C\u002Fstrong> Interceptors run after guards, so a rejected request never reaches even the setup code in an interceptor — only the exception-filter layer sees it.",{"id":331,"html":332,"text":332,"type":147,"level":31},"b52","Best practices",{"id":334,"type":153,"items":335,"ordered":18},"b53",[336,337,338,339,340],"\u003Cstrong>Keep guards to yes\u002Fno authorization decisions.\u003C\u002Fstrong> If you find yourself mutating the request object inside a guard, that logic usually belongs in middleware (before routing) or an interceptor (after the handler is chosen) instead.","\u003Cstrong>Prefer metadata-driven guards over hardcoded checks.\u003C\u002Fstrong> A \u003Ccode>@Roles()\u003C\u002Fcode>\u002F\u003Ccode>Reflector\u003C\u002Fcode> pair scales to new routes with zero changes to the guard itself; an \u003Ccode>if (handler.name === &quot;remove&quot;)\u003C\u002Fcode> branch doesn&#39;t.","\u003Cstrong>Register anything with a dependency via \u003Ccode>APP_GUARD\u003C\u002Fcode>, never \u003Ccode>new Guard()\u003C\u002Fcode>.\u003C\u002Fstrong> It&#39;s the difference between a guard that&#39;s testable and overridable, and one that silently can&#39;t be either.","\u003Cstrong>Fail closed, not open.\u003C\u002Fstrong> If a metadata lookup comes back \u003Ccode>undefined\u003C\u002Fcode> because of a wiring mistake, decide what that \u003Cem>should\u003C\u002Fem> mean deliberately (usually: deny), rather than letting \u003Ccode>!required\u003C\u002Fcode> accidentally mean &quot;allow everyone.&quot;","\u003Cstrong>One guard, one concern.\u003C\u002Fstrong> A \u003Ccode>ThrottleGuard\u003C\u002Fcode> and a \u003Ccode>RolesGuard\u003C\u002Fcode> composed via \u003Ccode>@UseGuards(ThrottleGuard, RolesGuard)\u003C\u002Fcode> are each easier to test and reuse than one guard doing both jobs.",{"id":342,"html":343,"text":343,"type":147,"level":31},"b54","FAQ",{"id":345,"html":346,"text":346,"type":147,"level":43},"b55","Do guards run before or after middleware?",{"id":348,"html":349,"type":136},"b56","\u003Cp>After. Middleware runs first and doesn&#39;t know which controller or handler will end up serving the request; guards run once routing has resolved to a specific handler, which is what lets a guard call \u003Ccode>context.getHandler()\u003C\u002Fcode>.\u003C\u002Fp>",{"id":351,"html":352,"text":352,"type":147,"level":43},"b57","Can a guard inject a service?",{"id":354,"html":355,"type":136},"b58","\u003Cp>Yes, as long as Nest constructs the guard — via \u003Ccode>@UseGuards(SomeGuard)\u003C\u002Fcode> (the class) or an \u003Ccode>APP_GUARD\u003C\u002Fcode> provider. A guard instance you build yourself with \u003Ccode>new\u003C\u002Fcode> gets none of Nest&#39;s dependency injection.\u003C\u002Fp>",{"id":357,"html":358,"text":359,"type":147,"level":43},"b59","Can I use \u003Ccode>@UseGuards()\u003C\u002Fcode> more than once on the same controller?","Can I use @UseGuards() more than once on the same controller?",{"id":361,"html":362,"type":136},"b60","\u003Cp>Yes — \u003Ccode>@UseGuards(A, B, C)\u003C\u002Fcode> runs them in that order, and you can also stack a class-level \u003Ccode>@UseGuards()\u003C\u002Fcode> with a method-level one; both apply, in the global → controller → method order described in Stage 5.\u003C\u002Fp>",{"id":364,"html":365,"text":366,"type":147,"level":43},"b61","What does a guard returning \u003Ccode>false\u003C\u002Fcode> actually send to the client?","What does a guard returning false actually send to the client?",{"id":368,"html":369,"type":136},"b62","\u003Cp>A \u003Ccode>403 Forbidden\u003C\u002Fcode> by default, handled by Nest&#39;s built-in exception layer. Throw a specific \u003Ccode>HttpException\u003C\u002Fcode> subclass from inside the guard if you need a different status code or a custom error body.\u003C\u002Fp>",{"id":371,"html":372,"text":373,"type":147,"level":43},"b63","Is \u003Ccode>Reflector\u003C\u002Fcode> only for guards?","Is Reflector only for guards?",{"id":375,"html":376,"type":136},"b64","\u003Cp>No — interceptors and custom decorators use it too, for the same reason: reading metadata attached to a handler or class via \u003Ccode>SetMetadata\u003C\u002Fcode>. Guards are just its most common consumer, because &quot;does this route require X&quot; is the canonical authorization question.\u003C\u002Fp>",{"id":378,"html":379,"text":379,"type":147,"level":31},"b65","Cheat sheet",{"id":381,"code":382,"type":194,"language":195,"highlight":383},"b66","\u002F\u002F A guard is a provider that implements CanActivate.\n@Injectable()\nexport class MyGuard implements CanActivate {\n  constructor(private reflector: Reflector) {} \u002F\u002F real DI, if registered correctly\n\n  canActivate(ctx: ExecutionContext): boolean | Promise\u003Cboolean> {\n    const req = ctx.switchToHttp().getRequest();       \u002F\u002F the underlying request\n    const handler = ctx.getHandler();                  \u002F\u002F the exact route method\n    const controller = ctx.getClass();                 \u002F\u002F the controller class\n    return true; \u002F\u002F or false, or throw a specific HttpException\n  }\n}\n\n\u002F\u002F Metadata decorator + read-back\nexport const ROLES_KEY = \"roles\";\nexport const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);\n\u002F\u002F method metadata wins over class metadata:\nreflector.getAllAndOverride\u003Cstring[]>(ROLES_KEY, [ctx.getHandler(), ctx.getClass()]);\n\u002F\u002F both levels contribute instead:\nreflector.getAllAndMerge\u003Cstring[]>(ROLES_KEY, [ctx.getHandler(), ctx.getClass()]);\n\n\u002F\u002F Registration — pick based on whether the guard has dependencies\n@UseGuards(MyGuard)              \u002F\u002F per-route or per-controller, always through DI\napp.useGlobalGuards(new MyGuard()); \u002F\u002F global, but NO dependency injection\n{ provide: APP_GUARD, useClass: MyGuard } \u002F\u002F global, WITH dependency injection",[],{"id":385,"head":386,"rows":391,"type":409},"b67",[387,388,389,390],"Scope","Registration","Runs when","Gets DI?",[392,397,401,405],[393,394,395,396],"Method","\u003Ccode>@UseGuards(G)\u003C\u002Fcode> on a handler","Only that route","Yes",[398,399,400,396],"Controller","\u003Ccode>@UseGuards(G)\u003C\u002Fcode> on a class","Every route in that controller",[402,403,404,396],"Global (correct)","\u003Ccode>APP_GUARD\u003C\u002Fcode> provider","Every route in the app",[406,407,404,408],"Global (limited)","\u003Ccode>app.useGlobalGuards(new G())\u003C\u002Fcode>","No","table",{"id":411,"html":412,"type":136},"b68","\u003Cp>Execution order for one request: \u003Cstrong>global → controller → method\u003C\u002Fstrong>, each one an AND — the first \u003Ccode>false\u003C\u002Fcode> (or thrown exception) stops the chain immediately.\u003C\u002Fp>",{"id":414,"html":415,"text":415,"type":147,"level":31},"b69","Key takeaways",{"id":417,"type":153,"items":418,"ordered":18},"b70",[419,420,421,422,423],"A guard is a DI provider with a \u003Ccode>canActivate(context: ExecutionContext)\u003C\u002Fcode> method — not a bare function, and not free of the container&#39;s rules.","\u003Ccode>ExecutionContext\u003C\u002Fcode> gives a guard the underlying request (via \u003Ccode>switchToHttp()\u003C\u002Fcode>\u002F\u003Ccode>switchToWs()\u003C\u002Fcode>\u002F\u003Ccode>switchToRpc()\u003C\u002Fcode>) and the exact handler\u002Fclass Nest is about to invoke (via \u003Ccode>getHandler()\u003C\u002Fcode>\u002F\u003Ccode>getClass()\u003C\u002Fcode>).","\u003Ccode>Reflector.getAllAndOverride()\u003C\u002Fcode> reads metadata attached with a custom decorator, checking the method before the class, and returns the first match — use \u003Ccode>getAllAndMerge()\u003C\u002Fcode> when you want both to contribute instead.","\u003Ccode>app.useGlobalGuards(new G())\u003C\u002Fcode> skips dependency injection entirely; use the \u003Ccode>APP_GUARD\u003C\u002Fcode> provider token for any global guard with constructor dependencies.","Guards compose as global → controller → method, and it&#39;s a strict AND: any single \u003Ccode>false\u003C\u002Fcode> or thrown exception stops the request before it reaches the next guard, any interceptor, or the handler.",{"id":425,"html":426,"text":426,"type":147,"level":31},"b71","Ending",{"id":428,"html":429,"type":136},"b72","\u003Cp>The guard from the intro wasn&#39;t wrong about \u003Cem>what\u003C\u002Fem> it wanted to check — it was wrong about \u003Cem>how it was born\u003C\u002Fem>. \u003Ccode>new RolesGuard(...)\u003C\u002Fcode> and \u003Ccode>{ provide: APP_GUARD, useClass: RolesGuard }\u003C\u002Fcode> compile to the same class doing the same check, and only one of them lets Nest&#39;s container do its job. That&#39;s the whole lesson of guards: they look like plain functions with a \u003Ccode>canActivate\u003C\u002Fcode> name, but every guarantee they can offer — real dependencies, testability, metadata lookups through \u003Ccode>ExecutionContext\u003C\u002Fcode> — depends on Nest actually building them. Next time a &quot;protected&quot; route turns out not to be, check the registration line before you touch the guard&#39;s logic.\u003C\u002Fp>",{"id":431,"html":432,"type":136},"b73","\u003Cp>What&#39;s the strangest guard bug you&#39;ve chased down — a missing dependency, a metadata key typo, or something else? Drop it in the comments.\u003C\u002Fp>",{"id":434,"html":435,"type":136},"b74","\u003C!-- playground:start -->",{"id":437,"html":438,"text":438,"type":147,"level":31},"b75","🎮 Try it yourself",{"id":440,"html":441,"type":136},"b76","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":443,"html":444,"type":136},"b77","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":446,"html":447,"type":136},"b78","\u003C!-- playground:end -->",{"id":449,"html":450,"type":136},"b79","\u003C!-- quiz:start -->",{"id":452,"html":453,"text":453,"type":147,"level":31},"b80","🧠 Test yourself",{"id":455,"html":456,"type":136},"b81","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":458,"html":459,"type":136},"b82","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":461,"html":462,"type":136},"b83","\u003C!-- quiz:end -->",{"id":464,"type":465},"b84","divider",{"id":467,"html":468,"type":136},"b85","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":470,"html":471,"type":136},"b86","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":473,"type":153,"items":474,"ordered":18},"b87",[475,476,477],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","A code review flags a \"protected\" admin endpoint. The controller has `@UseGuards(RolesGuard)` on the class, `RolesGuard` injects a `UserService` to check role hierarchy, and it's registered application-wide with `app.useGlobalGuards(new RolesGuard(reflector, userService))` in `main.ts`. It looks careful. It is also broken: `userService` is `undefined` inside the guard, on every single request, and no test would have caught it because the app never crashed — it just let every request through the branch that assumes a user has no elevated role.\n\nNothing here is a typo. The guard class is correct. The decorator is correct. The one thing wrong is *how* the guard was registered — and until you know what a guard actually is to Nest's container, that line looks completely reasonable.\n\nThis is written against **NestJS 12.0.x** (verified September 2026, current `@nestjs\u002Fcore` release, [v12.0.4](https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases)). The `CanActivate` interface, `ExecutionContext`, and `Reflector` covered here have been stable since well before v9 and are untouched by v12's ESM and Standard Schema changes — nothing in this article is version-fragile.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain what a guard actually is — a DI-instantiated class, not a function you happen to call `canActivate`\n- Read and use `ExecutionContext` to get at the request, the handler, and the controller class from inside a guard\n- Build a `Reflector`-backed `@Roles()` decorator and understand exactly why the method's metadata overrides the class's\n- Register a global guard the *right* way (`APP_GUARD`) so it keeps its dependency injection\n- Predict, for any combination of global\u002Fcontroller\u002Fmethod guards, the exact order they run in and what \"all must pass\" means\n\n## Who this is for\n\nYou've written a NestJS controller, used `@UseGuards()` at least once, and you know what `@Injectable()` does. If you haven't read the [NestJS request lifecycle](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-request-lifecycle-explained-with-cheat-sheet-227l) episode of this series, it's a useful map of where guards sit relative to middleware, interceptors, and pipes — but this article is self-contained.\n\n## Table of contents\n\n- [The problem: a guard that quietly can't do its job](#the-problem-a-guard-that-quietly-cant-do-its-job)\n- [The mental model: a guard is a provider, not a function](#the-mental-model-a-guard-is-a-provider-not-a-function)\n- [Stage 1: the smallest correct guard](#stage-1-the-smallest-correct-guard)\n- [Stage 2: ExecutionContext, properly](#stage-2-executioncontext-properly)\n- [Stage 3: Reflector and a real `@Roles()` decorator](#stage-3-reflector-and-a-real-roles-decorator)\n- [Stage 4: registering a global guard without losing DI](#stage-4-registering-a-global-guard-without-losing-di)\n- [Stage 5: composing guards — order and \"all must pass\"](#stage-5-composing-guards--order-and-all-must-pass)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## The problem: a guard that quietly can't do its job\n\nHere's the guard from the intro, in full:\n\n```typescript\n@Injectable()\nexport class RolesGuard implements CanActivate {\n  constructor(\n    private reflector: Reflector,\n    private userService: UserService, \u002F\u002F meant to check role hierarchy\n  ) {}\n\n  canActivate(context: ExecutionContext): boolean {\n    const required = this.reflector.get\u003Cstring[]>(\"roles\", context.getHandler());\n    if (!required) return true;\n    const { user } = context.switchToHttp().getRequest();\n    \u002F\u002F userService is undefined here — this branch always throws or always no-ops\n    return this.userService.hasAnyRole(user, required);\n  }\n}\n```\n\nAnd here's how it was wired up, because \"global\" sounded like the right word for \"runs on every route\":\n\n```typescript\n\u002F\u002F main.ts — looks reasonable, is quietly broken\nconst app = await NestFactory.create(AppModule);\napp.useGlobalGuards(new RolesGuard(new Reflector(), \u002F* userService? *\u002F undefined));\n```\n\n`app.useGlobalGuards()` takes an *instance*. Nothing about that instance goes through Nest's dependency injection — you constructed it yourself, with `new`, outside any module, before the application's providers even exist to hand it a real `UserService`. Nest will happily call `canActivate()` on this object for every request. It just can't give it the dependency the guard was written to use.\n\nThe fix isn't a different guard. It's a different *registration*, covered in [Stage 4](#stage-4-registering-a-global-guard-without-losing-di). But to see why that fix works, you need the actual mental model of what a guard is.\n\n## The mental model: a guard is a provider, not a function\n\n**The mental model:** a NestJS guard is an ordinary DI provider — the same kind of class as a service — that additionally implements one method, `canActivate(context: ExecutionContext)`, which Nest calls immediately before it would otherwise invoke your route handler. If `canActivate` resolves to `true`, the request keeps moving toward interceptors, pipes, and the handler. If it resolves to `false`, or throws, the handler never runs and Nest hands the request straight to the exception-filter layer.\n\nThree consequences fall out of that one sentence, and they're the three things the intro's bug got wrong:\n\n1. **A guard only gets real dependency injection if Nest constructs it.** `@UseGuards(RolesGuard)` (passing the *class*) lets Nest instantiate it through the container, resolving its constructor arguments normally. `new RolesGuard(...)` (passing an *instance* you built) does not — you're on your own for every dependency.\n2. **A guard doesn't see \"a request\" in the abstract — it sees an `ExecutionContext`.** That's a wrapper Nest builds fresh for every incoming call, giving the guard a uniform way to reach the underlying request *and* to ask \"what handler and class is Nest about to invoke?\" — which is exactly what a guard needs to look up route-specific metadata.\n3. **A guard's decision is binary, not additive.** It either lets a request through or it doesn't. Anything more nuanced than \"yes\u002Fno\" — attaching data, transforming the body — is an interceptor's or a pipe's job, not a guard's.\n\n## Stage 1: the smallest correct guard\n\nThe simplest guard that compiles and does something real:\n\n```typescript\nimport { CanActivate, ExecutionContext, Injectable } from \"@nestjs\u002Fcommon\";\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n  canActivate(context: ExecutionContext): boolean {\n    const request = context.switchToHttp().getRequest();\n    return Boolean(request.headers.authorization);\n  }\n}\n```\n\nApply it to one route:\n\n```typescript\n@UseGuards(AuthGuard)\n@Get(\"profile\")\ngetProfile(@Req() req: Request) {\n  return req.user;\n}\n```\n\n**Key concept:** `canActivate` can return `boolean`, `Promise\u003Cboolean>`, or `Observable\u003Cboolean>` — Nest awaits or subscribes to whichever you give it. A guard that calls a database or an external identity provider to check a session is completely normal; just make it `async canActivate(...): Promise\u003Cboolean>` and Nest will wait for it before deciding.\n\n## Stage 2: ExecutionContext, properly\n\n`ExecutionContext` is the single argument every guard, interceptor, and exception filter receives, and it answers two different questions:\n\n**\"What kind of call is this, and what's the underlying request object?\"** — via `switchToHttp()`, `switchToRpc()`, or `switchToWs()`. Most guards only ever call `context.switchToHttp().getRequest()`, but the same guard class *can* run in front of a WebSocket gateway or a microservice handler if you check `context.getType()` first and branch — that's what makes `ExecutionContext` a context, not just an HTTP request wrapper.\n\n**\"Which handler and which class is Nest about to call?\"** — via `context.getHandler()` (the specific route method, as a function reference) and `context.getClass()` (the controller class). This half is what makes metadata-driven guards possible, because it's the *only* way a guard can ask \"does this specific route carry a `@Roles(...)` decorator?\" — the guard runs once per registration, but `getHandler()`\u002F`getClass()` tell it which route it's currently deciding for.\n\n```typescript\ncanActivate(context: ExecutionContext): boolean {\n  const handlerName = context.getHandler().name;      \u002F\u002F e.g. \"getProfile\"\n  const controllerName = context.getClass().name;     \u002F\u002F e.g. \"UsersController\"\n  const request = context.switchToHttp().getRequest();\n  \u002F\u002F ...\n}\n```\n\n**Key concept:** `getHandler()` and `getClass()` return the raw function\u002Fclass references, not strings — they exist so you can hand them to `Reflector`, which looks up metadata *attached to those exact references*. That's the bridge to Stage 3.\n\n## Stage 3: Reflector and a real `@Roles()` decorator\n\nHardcoding a role check per route doesn't scale, and neither does branching on `handler.name` — a rename breaks it silently. The idiomatic pattern is a custom decorator that attaches metadata, and a `Reflector` that reads it back inside a guard.\n\n```typescript\n\u002F\u002F roles.decorator.ts\nimport { SetMetadata } from \"@nestjs\u002Fcommon\";\n\nexport const ROLES_KEY = \"roles\";\nexport const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);\n```\n\n```typescript\n\u002F\u002F roles.guard.ts\nimport { CanActivate, ExecutionContext, Injectable } from \"@nestjs\u002Fcommon\";\nimport { Reflector } from \"@nestjs\u002Fcore\";\nimport { ROLES_KEY } from \".\u002Froles.decorator\";\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n  constructor(private reflector: Reflector) {}\n\n  canActivate(context: ExecutionContext): boolean {\n    const required = this.reflector.getAllAndOverride\u003Cstring[]>(ROLES_KEY, [\n      context.getHandler(),\n      context.getClass(),\n    ]);\n    if (!required || required.length === 0) return true; \u002F\u002F no @Roles() = open route\n\n    const { user } = context.switchToHttp().getRequest();\n    return required.some((role) => user?.roles?.includes(role));\n  }\n}\n```\n\n```typescript\n@Controller(\"orders\")\n@Roles(\"editor\") \u002F\u002F class-level default\nexport class OrdersController {\n  @Get()\n  list() { \u002F* requires \"editor\" — inherits the class default *\u002F }\n\n  @Delete(\":id\")\n  @Roles(\"admin\") \u002F\u002F method-level override\n  remove() { \u002F* requires \"admin\" — this wins over the class's \"editor\" *\u002F }\n}\n```\n\n**Key concept:** `getAllAndOverride(key, [handler, class])` checks the handler first, then the class, and returns the **first one it finds** — it does not merge arrays. That order in the array is why a method-level `@Roles(\"admin\")` completely replaces the class-level `@Roles(\"editor\")` rather than requiring both. If you actually want both handler and class metadata combined, `Reflector` also has `getAllAndMerge()`, which concatenates arrays instead of short-circuiting — reach for it explicitly when \"either level can add a role\" is the behavior you want.\n\n## Stage 4: registering a global guard without losing DI\n\nBack to the intro's bug. `app.useGlobalGuards(new RolesGuard(...))` builds the guard outside the container, so any constructor dependency has to be supplied by hand — which is exactly what went wrong. The fix is to register the guard as a provider, using the `APP_GUARD` injection token from `@nestjs\u002Fcore`:\n\n```typescript\nimport { APP_GUARD } from \"@nestjs\u002Fcore\";\n\n@Module({\n  providers: [\n    RolesGuard,\n    { provide: APP_GUARD, useClass: RolesGuard },\n  ],\n})\nexport class AppModule {}\n```\n\nBecause `RolesGuard` is now a normal provider, Nest resolves its constructor the usual way — `Reflector` (and, in the intro's case, a real `UserService`) get injected correctly, module-scoped providers work, and `Test.createTestingModule` can `overrideProvider(RolesGuard)` in tests the same way it overrides any other dependency, as covered in this series' [testing module episode](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-testing-module-provider-overrides-with-cheat-sheet-1o79). None of that is available to a guard built with `new` in `main.ts`.\n\n**Key concept:** `app.useGlobalGuards()` still exists and still works for a guard with zero dependencies — it's not deprecated. The rule is narrower and easy to remember: the moment a guard's constructor needs anything Nest would normally inject, register it through `APP_GUARD`, not `useGlobalGuards()`.\n\n## Stage 5: composing guards — order and \"all must pass\"\n\nA single request can pass through guards registered at three different scopes at once: global (`APP_GUARD`, or `useGlobalGuards()`), controller (`@UseGuards()` on the class), and method (`@UseGuards()` on the handler). Nest runs them in that exact order — global, then controller, then method — and within one `@UseGuards(A, B)` call, in the order listed.\n\nThis composition is a logical **AND**, not a fallback chain: every guard in the sequence must return (or resolve to) `true`, or the request is rejected at the first one that doesn't. There's no \"guard B can override guard A's denial\" — a single `false` anywhere in the chain ends the request immediately, and every guard after it, plus every interceptor and pipe, is skipped entirely.\n\n```typescript\n@UseGuards(ThrottleGuard, RolesGuard) \u002F\u002F ThrottleGuard runs first\n@Controller(\"orders\")\nexport class OrdersController {}\n```\n\nIf a global `AuthGuard` is also registered via `APP_GUARD`, the real order for any route on this controller is: `AuthGuard` → `ThrottleGuard` → `RolesGuard` → (method-level guards, if any) → interceptors → pipes → the handler.\n\n## Edge cases and gotchas\n\n- **A guard that throws vs. a guard that returns `false`.** Returning `false` produces a generic `403 Forbidden`. Throwing a specific exception — `throw new UnauthorizedException(\"Session expired\")` — gives the client (and your logs) a far more useful signal, and is the idiomatic choice for anything beyond \"just deny it.\"\n- **`Reflector` needs the exact same metadata key everywhere.** `SetMetadata(ROLES_KEY, ...)` and `reflector.getAllAndOverride(ROLES_KEY, ...)` must use the identical string (or, better, the same exported constant). A typo in one spot means the guard silently sees `undefined` and treats the route as unrestricted — this fails open, which is the worst direction for an auth check to fail.\n- **`getAllAndOverride` vs `getAllAndMerge`.** Covered in Stage 3, but worth repeating because it's the single most common `Reflector` mistake: reach for `getAllAndOverride` when a method should be able to fully replace a class default, and `getAllAndMerge` when both levels should contribute.\n- **WebSocket and microservice guards need a type check.** `context.switchToHttp()` throws if the current call isn't actually HTTP. A guard meant to run across transports should branch on `context.getType()` (`\"http\"`, `\"ws\"`, `\"rpc\"`) before picking which `switchTo*()` to call.\n- **A denied guard skips the \"before\" half of interceptors too.** Interceptors run after guards, so a rejected request never reaches even the setup code in an interceptor — only the exception-filter layer sees it.\n\n## Best practices\n\n- **Keep guards to yes\u002Fno authorization decisions.** If you find yourself mutating the request object inside a guard, that logic usually belongs in middleware (before routing) or an interceptor (after the handler is chosen) instead.\n- **Prefer metadata-driven guards over hardcoded checks.** A `@Roles()`\u002F`Reflector` pair scales to new routes with zero changes to the guard itself; an `if (handler.name === \"remove\")` branch doesn't.\n- **Register anything with a dependency via `APP_GUARD`, never `new Guard()`.** It's the difference between a guard that's testable and overridable, and one that silently can't be either.\n- **Fail closed, not open.** If a metadata lookup comes back `undefined` because of a wiring mistake, decide what that *should* mean deliberately (usually: deny), rather than letting `!required` accidentally mean \"allow everyone.\"\n- **One guard, one concern.** A `ThrottleGuard` and a `RolesGuard` composed via `@UseGuards(ThrottleGuard, RolesGuard)` are each easier to test and reuse than one guard doing both jobs.\n\n## FAQ\n\n### Do guards run before or after middleware?\n\nAfter. Middleware runs first and doesn't know which controller or handler will end up serving the request; guards run once routing has resolved to a specific handler, which is what lets a guard call `context.getHandler()`.\n\n### Can a guard inject a service?\n\nYes, as long as Nest constructs the guard — via `@UseGuards(SomeGuard)` (the class) or an `APP_GUARD` provider. A guard instance you build yourself with `new` gets none of Nest's dependency injection.\n\n### Can I use `@UseGuards()` more than once on the same controller?\n\nYes — `@UseGuards(A, B, C)` runs them in that order, and you can also stack a class-level `@UseGuards()` with a method-level one; both apply, in the global → controller → method order described in Stage 5.\n\n### What does a guard returning `false` actually send to the client?\n\nA `403 Forbidden` by default, handled by Nest's built-in exception layer. Throw a specific `HttpException` subclass from inside the guard if you need a different status code or a custom error body.\n\n### Is `Reflector` only for guards?\n\nNo — interceptors and custom decorators use it too, for the same reason: reading metadata attached to a handler or class via `SetMetadata`. Guards are just its most common consumer, because \"does this route require X\" is the canonical authorization question.\n\n## Cheat sheet\n\n```typescript\n\u002F\u002F A guard is a provider that implements CanActivate.\n@Injectable()\nexport class MyGuard implements CanActivate {\n  constructor(private reflector: Reflector) {} \u002F\u002F real DI, if registered correctly\n\n  canActivate(ctx: ExecutionContext): boolean | Promise\u003Cboolean> {\n    const req = ctx.switchToHttp().getRequest();       \u002F\u002F the underlying request\n    const handler = ctx.getHandler();                  \u002F\u002F the exact route method\n    const controller = ctx.getClass();                 \u002F\u002F the controller class\n    return true; \u002F\u002F or false, or throw a specific HttpException\n  }\n}\n\n\u002F\u002F Metadata decorator + read-back\nexport const ROLES_KEY = \"roles\";\nexport const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);\n\u002F\u002F method metadata wins over class metadata:\nreflector.getAllAndOverride\u003Cstring[]>(ROLES_KEY, [ctx.getHandler(), ctx.getClass()]);\n\u002F\u002F both levels contribute instead:\nreflector.getAllAndMerge\u003Cstring[]>(ROLES_KEY, [ctx.getHandler(), ctx.getClass()]);\n\n\u002F\u002F Registration — pick based on whether the guard has dependencies\n@UseGuards(MyGuard)              \u002F\u002F per-route or per-controller, always through DI\napp.useGlobalGuards(new MyGuard()); \u002F\u002F global, but NO dependency injection\n{ provide: APP_GUARD, useClass: MyGuard } \u002F\u002F global, WITH dependency injection\n```\n\n| Scope | Registration | Runs when | Gets DI? |\n| --- | --- | --- | --- |\n| Method | `@UseGuards(G)` on a handler | Only that route | Yes |\n| Controller | `@UseGuards(G)` on a class | Every route in that controller | Yes |\n| Global (correct) | `APP_GUARD` provider | Every route in the app | Yes |\n| Global (limited) | `app.useGlobalGuards(new G())` | Every route in the app | No |\n\nExecution order for one request: **global → controller → method**, each one an AND — the first `false` (or thrown exception) stops the chain immediately.\n\n## Key takeaways\n\n- A guard is a DI provider with a `canActivate(context: ExecutionContext)` method — not a bare function, and not free of the container's rules.\n- `ExecutionContext` gives a guard the underlying request (via `switchToHttp()`\u002F`switchToWs()`\u002F`switchToRpc()`) and the exact handler\u002Fclass Nest is about to invoke (via `getHandler()`\u002F`getClass()`).\n- `Reflector.getAllAndOverride()` reads metadata attached with a custom decorator, checking the method before the class, and returns the first match — use `getAllAndMerge()` when you want both to contribute instead.\n- `app.useGlobalGuards(new G())` skips dependency injection entirely; use the `APP_GUARD` provider token for any global guard with constructor dependencies.\n- Guards compose as global → controller → method, and it's a strict AND: any single `false` or thrown exception stops the request before it reaches the next guard, any interceptor, or the handler.\n\n## Ending\n\nThe guard from the intro wasn't wrong about *what* it wanted to check — it was wrong about *how it was born*. `new RolesGuard(...)` and `{ provide: APP_GUARD, useClass: RolesGuard }` compile to the same class doing the same check, and only one of them lets Nest's container do its job. That's the whole lesson of guards: they look like plain functions with a `canActivate` name, but every guarantee they can offer — real dependencies, testability, metadata lookups through `ExecutionContext` — depends on Nest actually building them. Next time a \"protected\" route turns out not to be, check the registration line before you touch the guard's logic.\n\nWhat's the strangest guard bug you've chased down — a missing dependency, a metadata key typo, or something else? Drop it in the comments.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":48,"canonical":480,"description":109},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector","01a0ddae-48ad-75a7-9839-4b6d112301dd",{"name":483,"part":484,"total":484,"items":485},"NestJS Deep Dive",5,[486,490,494,498,502],{"slug":487,"title":488,"publishedAt":489,"readingMinutes":92},"nestjs-weekly-request-lifecycle","NestJS Request Lifecycle Explained (with Cheat Sheet)","2026-08-28T17:01:41.941Z",{"slug":491,"title":492,"publishedAt":493,"readingMinutes":88},"nestjs-weekly-dependency-injection-providers-scopes","NestJS Dependency Injection Explained (with Cheat Sheet)","2026-09-13T08:40:18.434Z",{"slug":495,"title":496,"publishedAt":497,"readingMinutes":88},"nestjs-weekly-module-encapsulation-exports","NestJS Module Encapsulation Explained (with Cheat Sheet)","2026-09-13T08:40:50.070Z",{"slug":499,"title":500,"publishedAt":501,"readingMinutes":92},"nestjs-weekly-testing-provider-overrides","NestJS Testing Module: Provider Overrides (with Cheat Sheet)","2026-09-18T17:23:03.842Z",{"slug":46,"title":48,"publishedAt":111,"readingMinutes":88},{"id":504,"locked":18},"01a0ddae-48e5-719c-b789-ed0a33c4bf65",[506],{"id":45,"slug":46,"title":48,"_count":507},{"questions":51},[509],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":511,"questionCount":51},{"questions":51}]