Implementing Organization‑Scoped Multi‑Tenancy in a NestJS Monorepo – Fixing the Req Import & Guard Issue
TL;DR: I updated the AdministradorasController to correctly import Req and adjust the UseGuards decorator, which stopped NestJS from throwing “Cannot read property ‘user’ of undefined” when resolving the tenant from the request. The change unlocked proper organization scoping across all API routes.
The Problem
Our monorepo’s API layer (apps/api) is being refactored to support multi‑tenancy based on an organization_id header. The AdministradorasController is one of the first endpoints we scoped. After adding a custom OrganizationGuard, every request to /administradoras started failing with:
Error: Cannot read property 'user' of undefined
at OrganizationGuard.canActivate (src/common/guards/organization.guard.ts:42:23)
at Reflector.get (node_modules/@nestjs/core/reflector/reflector.js:45:15)
The guard expects the request object (Req) to be injected, but Nest was passing undefined. The stack trace pointed to the controller’s decorator usage, not the guard itself.
What I Tried First
My initial attempt was to add the guard at the module level:
@Module({
controllers: [AdministradorasController],
providers: [OrganizationGuard],
})
export class AdministradorasModule {}
I also tried importing Req inside the guard directly:
import { Request } from 'express';
...
canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest<Request>();
// ...
}
Both approaches compiled, but the runtime error persisted. The guard never received the request because the controller’s method signature didn’t expose it, and the global guard registration didn’t bind the request context correctly.
The Implementation
The root cause turned out to be a missing Req import in the controller and an incorrectly applied UseGuards decorator. The diff from commit 14da996a shows the exact changes:
Before (broken)
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AdministradorasService } from './administradoras.service';
@Controller('administradoras')
export class AdministradorasController {
constructor(private readonly service: AdministradorasService) {}
@Get()
@UseGuards(OrganizationGuard) // ← Guard applied, but no Req in method
findAll() {
return this.service.findAll();
}
}
After (fixed)
import {
Controller,
Get,
UseGuards,
Req, // <-- added import
} from '@nestjs/common';
import { AdministradorasService } from './administradoras.service';
import { OrganizationGuard } from '../../common/guards/organization.guard';
@Controller('administradoras')
export class AdministradorasController {
constructor(private readonly service: AdministradorasService) {}
@Get()
@UseGuards(OrganizationGuard)
findAll(@Req() request: Request) { // <-- inject request
// The guard already validated organization_id; we just forward it.
const orgId = request.headers['organization_id'];
return this.service.findAllByOrg(orgId);
}
}
Key points in the diff
| File | Change |
|---|---|
apps/api/src/administradoras/administradoras.controller.ts |
Added Req to the import list and injected it into the findAll method. |
| Same file | Imported OrganizationGuard from the shared guards folder for clarity. |
| Same file | Updated the service call to findAllByOrg(orgId) to demonstrate tenant‑aware data fetching. |
Why this works: Nest’s @Req() decorator pulls the underlying Express Request object from the execution context. By explicitly declaring it in the method signature, the guard’s canActivate receives a fully populated request, allowing it to read organization_id without hitting undefined.
Guard Implementation (unchanged but worth showing)
@Injectable()
export class OrganizationGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest<Request>();
const orgId = req.headers['organization_id'];
if (!orgId) {
throw new ForbiddenException('Missing organization_id');
}
// Attach orgId to request for downstream handlers
(req as any).organizationId = orgId;
return true;
}
}
Additional Files Updated
The content‑automation scripts that generate weekly posts also needed a tiny bump to reference the new controller path, but those changes are purely markdown updates and don’t affect the runtime.
Key Takeaway
When building multi‑tenancy with NestJS, always inject the request object (@Req()) into any route that a guard depends on. A guard can only access what the execution context provides; if the controller method doesn’t expose the request, the guard sees undefined. This tiny import change prevented a cascade of runtime errors and kept the tenant resolution logic clean and testable.
What's Next
-
Service Layer Refactor: Move tenant filtering logic into a reusable
TenantRepositoryso every service can callrepo.findByOrg(orgId, ...). -
Integration Tests: Add e2e tests that send requests with and without
organization_idto verify the guard’s behavior. -
Docker Compose Update: Extend the
docker-compose.ymlto inject a mockORGANIZATION_IDenv var for local dev, ensuring developers can test tenant‑scoped routes without hitting the API gateway.
Roberto Luna Osorio – Full Stack Developer & Project Lead
Playa del Carmen, México
Tags: #vibecoding #buildinpublic #nestjs #typescript #multitenancy #docker
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/content-automation · 2026-09-09
#playadev #buildinpublic
Top comments (0)