What's the difference between Interceptor vs Middleware vs Filter in Nest.js?
As you already implied with your question, all three are very similar concepts and in a lot of cases it is hard to decide and comes down to your preferences. But I can give an overview of the differences:
Interceptors
Interceptors have access to response/request before and after the route handler is called.
Registration
- Directly in the controller class with
@UseInterceptors()
controller- or method-scoped - Globally with
app.useGlobalInterceptors()
inmain.ts
Examples
- LoggingInterceptor: Request before route handler and afterwards its result. Meassure time it takes.
- ResultMapping: Transform
null
to[]
or wrap result in a response object:users
->{users: users}
Conclusion
I like that the registration is closer to the route handlers compared to middleware. But there are some limitations, for example, you cannot set the response code or alter the response with Interceptors when you send the response
with the library-specific @Res()
object in your route handler, see docs.
Middleware
Middleware is called only before the route handler is called. You have access to the response object, but you don't have the result of the route handler. They are basically express middleware functions.
Registration
- In the module, very flexible way of choosing relevant routes (with wildcards, by method,...)
- Globally with
app.use()
inmain.ts
Examples
- FrontendMiddleware: redirect all routes except API to
index.html
, see this thread - You can use any express middleware that is out there. There are lots of libraries, e.g.
body-parser
ormorgan
Conclusion
The registration of middleware is very flexible, for example: apply to all routes but one etc. But since they are registered in the module, you might not realize it applies to your controller when you're looking at its methods. It's also great that you can make use of all the express middleware libraries that are out there.
Exception Filters
Exception Filters are called after the route handler and after the interceptors. They are the last place to make changes before a response goes out.
Registration
- Directly in the controller class with
@UseFilters()
controller- or method-scoped - Globally
app.useGlobalFilters()
in yourmain.ts
Examples
- UnauthorizedFilter: Map to an easy to understand message for the user
- NotFoundFilter: Map all routes that are not found (not part of your api) to your
index.html
.
Conclusion
The basic use case for exception filters are giving understandable error messages (hiding technical details). But there are also other creative ways of usage: When you serve a single page application, then typically all routes should redirect to index.html
except the routes of your API. Here, you can redirect on a NotFoundException
. Some might find this clever others hacky. Your choice. ;-)
So the execution order is:
Middleware -> Interceptors -> Route Handler -> Interceptors -> Exception Filter (if exception is thrown)
With all three of them, you can inject other dependencies (like services,...) in their constructor.
For those of us who "get it" better visually, I've created this NestJs pipeline digram based on the latest v6.10
version. Please feel free to point out any inaccuracies. I'll review and update it promptly, if needed.
I'm assuming that you mean Pipes instead of Filters as Filters are primarily tied to Exception Handling.
There is definitely some overlap as Middleware are a flexible way of composing any web application but are more of a generic concept (creating a stack of functions to build a pipeline). The others are Nest specific concepts and as such tie in a bit more naturally with things like Dependency Injection.
Pipes are used to transform input data (and optionally to do validation).
Interceptors are really neat because they can transform both data coming in and leaving your API. They give you the ability to mutate what the original handler would have returned through the use of observable streams. This is something that you would probably need to implement using two middlewares (on either side of the handler).
Use Pipes when you want to transform data coming in to a handler.
Use Interceptors when bi-directional transformation is required.
Use middlewares when you want to stick closer to the traditional (eg Express) way of building your web app or when you want to more broadly apply functionality to many handlers at once (there's less decorators floating around in your code).