Build1 publisher2 min readPublished
NestJS global guards built with new run every request without their injected services
NestJS guards passed to useGlobalGuards as hand-built instances run with undefined services on every request, a NestJS 12 tutorial shows. Registering through APP_GUARD fixes it. The tutorial's own code leaves open whether the broken guard lets requests through or throws.
The Engineer · Build desk

What happened
- A NestJS tutorial describes an admin controller protected by a RolesGuard that injects UserService and is also registered globally with app.useGlobalGuards(new RolesGuard(...)) in main.ts.
- useGlobalGuards() takes an instance, and one built with new in main.ts skips Nest's dependency injection because it exists before any providers do.
- The tutorial says userService was undefined inside the guard on every request while the app kept running without a crash.
- The walkthrough targets NestJS 12.0.x and @nestjs/core 12.0.4, and says the guard APIs involved have been stable since before version 9.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Global guards that inject services have to be re-registered as APP_GUARD providers, and the tutorial's fix leaves the guard classes themselves untouched.
- constraint Smoke tests and health checks that only hit routes without roles metadata will pass against a broken build, so only a request to a role-protected route surfaces the defect.
- contradiction A team cannot grade severity from this source alone: the throwing case is an outage on admin routes, and only the no-op case could become an authorization bypass.
The listing shows where the fault sits. Its `main.ts` line is `app.useGlobalGuards(new RolesGuard(new Reflector(), /* userService? */ undefined))`, so the service argument is a literal `undefined` [6]. Nest calls `canActivate()` on that object for every request [5]. It cannot give the object the `UserService` its constructor declared [5].
The tutorial says the line was written that way because "global" sounded like the right word for "runs on every route" [15]. For scope, it is [5]. According to the tutorial, the guard class and the decorator are both correct, and the registration is the only fault [16]. In its model, a guard is an ordinary dependency-injection provider, the same kind of class as a service, that also implements `canActivate(context: ExecutionContext)` for Nest to call just before the route handler [9]. "A guard only gets real dependency injection if Nest constructs it," the tutorial says [11].
It is harder to say what the broken guard does to a request. The tutorial says it "let every request through the branch that assumes a user has no elevated role" [2]. Read literally, that sentence has every user treated as unprivileged. The code comment in the same listing covers more ground: with `userService` undefined, "this branch always throws or always no-ops" [8]. The tutorial's lifecycle rule decides the first case. A guard that throws or resolves `false` stops the handler, and Nest passes the request to the exception-filter layer [10]. A throwing guard therefore refuses the request with an error, and the process keeps running [14]. A request gets past the check only if the missing-dependency path resolves `true`, and the excerpt does not show code that does that.
One line lets requests through on purpose. `if (!required) return true;` passes any handler without `roles` metadata before the code reaches `userService` [7]. The undefined service is touched only on routes that declare roles [17].
I think the review scope is small. A hand-built instance loses only what its constructor asked the container for. So a `useGlobalGuards(new ...)` call is a problem when the guard injects a service and is harmless when it injects nothing [18].
What to watch
- A reproduction of the scenario showing whether the controller-level @UseGuards(RolesGuard) copy still blocked non-admin requests while the global copy was broken.
- A published variant of the guard whose missing-dependency path resolves true, the case that would make this an authorization bypass instead of an error on admin routes.
- A NestJS change that warns at startup when useGlobalGuards receives an instance with undefined constructor dependencies.