Recentemente tenho me aprofundado nas ferramentas que o NestJS proporciona por conta de projetos que trabalho atualmente, hoje gostaria de trazer um pouco sobre os Guards e Interceptors.
No Nest, tanto Guards quanto Interceptors são ferramentas poderosas do ciclo de vida das requisições. Mas cada um tem seu papel, e entender quando usar um ou outro pode te ajudar a escrever código mais limpo, seguro e organizado.
Guards
São classes que implementam a interface CanActivate e são responsáveis por determinar se uma rota pode ou não ser acessada. Eles são executados antes do controllers e até antes dos interceptors também.
O que é possível fazer com Guards?
- Verificar se o usuário está autenticado (JWT, Keycloak, etc)
- Autorização por roles (admin, user, departamentos)
- Verificação de escopos de permissões
- Feature toggles (acesso a funcionalidades baseado em planos de executação)
Exemplo:
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private requiredRole: string) {} // Recebe a role necessária no construtor
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user // Assume que o user já foi validado
// Verifica se o usuer tem a role necessaria para processeguir
return user?.roles?.includes(this.requiredRole)
}
}
Aplicando o Guard no Controller
@UseGuard(RolesGuard)
@Get('protected')
public async getProtectEndpoint() {
return 'Só é acessado se passar pelo Guard'
};
Ou Globalmente
app.useGlobalGuards(new RolesGuard());
Algumas vantagens
- Rápidos e diretos, impedem que lógica do controller seja executada desnecessariamente.
- Centralizam regras de acesso
Tradeoff
- Não são ideias para transformar dados ou tratar respostas.
- Devem retornar apenas
true
oufalse
(outhrow
se for necessário lançar alguma exceção)
Interceptors
No NestJS é um decorator (@UseInterceptors()
) que permite você aplicar interceptadores em controladores ou métodos de rota. Esses interceptadores é uma poderosa ferramente que permite interceptar, transformar, monitorar ou até substituir o comportamento padrão de requisições e respostas. Eles são como "middlewares inteligentes" que o NestJS proporciona.
O @UseInterceptors()
permite executar lógica antes ou depois da execução de um handler (como um método de rota, por exemplo).
Como eles funcionam?
Quando aplicado a um método ou controller, o interceptor intercepta o ciclo de vida da requisição e te dá a chance de:Modificar os dados da requisição antes de chegar no handler
Modificar ou formatar a resposta antes dela ser enviada de volta ao cliente (ou front-end)
Medir tempo de execução
Adicionar lógica comum como logging, cache, autenticação e etc.
Exemplo bem genérico:
@UseInterceptors(MyInterceptor)
@Get('data')
public async getData() {
return { hello: 'World' }
}
Nest exemplo, a classe MyInterceptor
vai ser executada antes e/ou depois do método getData()
.
Um Interceptor básico
@Injectable()
export class LoggingIntercetor implement NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Antes da execução do handler')
const now = Date.now()
return next.handle().pipe(
tap(() => console.log(`Depois... ${Date.now - now}ms`))
)
}
}
Esse Interceptor apenas loga o tempo que o método levou para responder.
Tradeoff
- Podem ser complexos se usados em excesso, o aninhamento de vários interceptadores pode não ser uma boa escolha e é passível de se adotar uma outra estratégia.
- Lógica errada pode afetar todos os endpoints sem perceber.
O uso de Guards ou Interceptors é situacional, não existe certo ou errado, e sim o que seu projeto e sua aplicação demanda.
Mas ultimamente estou analisando os cenários dessa forma:
- Use Guards para decidir se a requisição continua.
- Use Interceptors para transformar o que acontece durante ou depois da execução.
Espero que tenha ficado claro e fácil de se entender o que cada ferramenta é capaz de fazer. Usado com parcimônia pode ser um grande aliado na hora do seu desenvolvimento!