Guards and Interceptors — cross-cutting infrastructure, not business logic
Guards authenticate and gate access — business rules stay in services where they can be tested.
nestjs-infra-guards-not-business-logic
Why it matters
| Stake | If ignored |
|---|---|
| Layer mixing |
|
How to fix
A Guard answers "is this request allowed?" — it does not run business operations, doesn't query several tables, doesn't change state. An Interceptor wraps the response (logging, error mapping, transformation) — it does not make business decisions.
Examples
ts
@Injectable()
export class CanCreateSiteGuard implements CanActivate {
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const user = req.user;
const siteCount = await this.sitesRepo.count({ where: { ownerId: user.id } });
if (siteCount >= 10 && !user.isPro) return false;
if (user.trialEndsAt < new Date()) {
await this.usersService.markTrialExpired(user.id); // ← side effect in guard!
return false;
}
return true;
}
}ts
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
return !!ctx.switchToHttp().getRequest().user;
}
}
@Injectable()
export class SitesService {
async create(userId: string, dto: CreateSiteDto): Promise<Site> {
await this.quotas.assertCanCreateSite(userId); // throws if not allowed
return this.repo.create({ ...dto, ownerId: userId });
}
}