
May 26, 2025
Understanding Global Exception Filters in NestJS — The Right Way (And the “It Still Works” Way)
Understanding Global Exception Filters in NestJS' The Right Way (And the It Still Works Way)
When working with NestJS, managing errors consistently across your application is a must. Exception filters allow you to intercept and format errors before they're returned to the client.
Today, I want to share something interesting I learned while implementing global exception filters, including a common pattern that works but isn't always recommended, and the preferred approach using NestJS's dependency injection system.
What Are Exception Filters in NestJS?
Exception filters in NestJS let you handle thrown errors in a structured and reusable way. For example, instead of showing a stack trace, you could return a clean JSON error like
{
"statusCode": 404,
"success": false,
"message": "Resource not found"
}
Applying Filters: Local vs Global
NestJS lets you apply exception filters:
- Locally: On a specific controller or route
- Globally: Across the entire application
Let's focus on global filters because they're powerful but a bit tricky.
Global Filters via useGlobalFilters() (Manual Injection)
This is how I initially set up my global filters:
import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
import { GlobalExceptionFilters } from "./exceptions/global.exception"
import { LogService } from "./log/log.service"
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const logService = app.get(LogService)
// Register filters manually
app.useGlobalFilters(new GlobalExceptionFilters(logService))
await app.listen(3000)
}
bootstrap()
Here, I manually inject LogService using app.get(LogService)
before passing it to the filters. This works because I'm creating the filter instances myself and not relying on NestJS to inject them.
Pros:
- Quick to set up
- Gives you full control over instantiation
Cons:
- Not ideal for large-scale apps
- Filters won't work in microservices or WebSockets
- Doesn't integrate well with Nest's lifecycle or testing tools
The Recommended Way: Registering with APP_FILTER
If your filter needs to use injected services like LogService, the preferred and scalable way is to register it in the module like this:
@Module({
providers: [
{
provide: APP_FILTER,
useClass: GlobalExceptionFilters,
},
],
})
export class AppModule {}
This way, NestJS takes care of creating and injecting your filter and its dependencies properly.
Pros:
- Works across HTTP, WebSocket, and microservices
- Fully integrated into NestJS' Dependency Injection system
- Cleaner and more scalable
Cons:
- Slightly more setup required
- Might feel less flexible if you're used to full control
Key Differences at a Glance
Approach | Can Inject Services? | Microservices Support | Recommended? |
useGlobalFilters(new ...) + app.get(...) | Yes (manual) | Not fully | Not ideal for scale |
APP_FILTER in a Module | Yes (automatic) | Yes | Yes |
Final Thoughts
If you're building a quick MVP or small API, manually registering your filters via useGlobalFilters()
might be good enough.
But if you're building a scalable, maintainable application, especially one that uses WebSockets or microservices, using APP_FILTER
is the right choice.
Pro Tip:
If you need multiple global filters, register them all in your providers array:
providers: [
{
provide: APP_FILTER,
useClass: GlobalExceptionFilters,
},
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
]
I hope this helps someone avoid the confusion I ran into. If you found this helpful or have a better pattern, feel free to reach out or drop a comment.
Happy coding!
Sample code: https://github.com/Intuneteq/nestjs-global-filters
139 views