Nest.js | Middleware, Pipes & Interceptors Explained By Example

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
there's a lot of confusion out there about the differences in sgs between middleware interceptors and pipes being able to correctly utilize each of these stages of the request response pipeline can mean really making your back end applications a lot less complex more scalable and in general nicer to work with so let's learn by example and build out each of these stages of the request response pipeline and really understand how this is working i'll see you there so to get a better understanding of the differences and similarities between these different stages of the nestjs request response pipeline i found this really useful diagram online from a stack overflow post from userdemi6 so i'll include a link to it in the description and thank you to him for creating this which i think is a great visual representation of this pipeline so let's go ahead and walk through this first before we jump into any code so we can kind of get a better understanding of what's happening when we send a request to an sjs server okay so let's start off at the very beginning we're going to fire off a client request to our server and the first bit of code that our request is going to meet is middleware so middleware is something that's not specific to sgs we have this in express too and it supports things like body parser cores morgan and additional other kind of third-party functionality that we can plug into an express server so this is the first thing that a client request hits so inside of middleware we have access to the express request and response objects so we can mutate them directly and in general middleware is a great place to do things like authentication where we can actually validate a json web token and additionally it's it's also great to attach different properties to the request object so say for example we have a request coming into the system and we want to add a user id to the request object so it's available later on in our application middleware is a great place to do that so next in the request pipeline here we have guards and guards are pretty simple they essentially just conform to this can activate interface and essentially it just tells us if we can continue on to the route handler based on some sort of authorization okay and now we move on to interceptors in my opinion interceptors are the most powerful form of the request response pipeline and that's because we have direct access to the request before it actually hits the route handler here and but we also can mutate the response after it has gone through the route handler here and the way we do this is because in the interceptor we can actually call next.handle and then pipe rxjs operators on the response so we can do things like mutating the response into a form that we prefer we can do things like logging how long the request took before and after the route handler we can do some global error handling there's really an endless amount of possibilities because we have access to the request before and the response after the round handler so in between this interceptor here before the route handler this is where pipes come into play and pipes are pretty straightforward they essentially just take in the request object from the route handler and it allows us to transform input data so we can essentially uh completely transform data that's going to the route handler into any form we'd like and we can also validate it so if it doesn't conform to a certain standard like json schema validation we can throw an error and prevent it from reaching the route handler and sgs has a number of built-in pipes like parseint to parse a string into an integer and so forth and the last part of our request response pipeline is exception filters and exception filters are essentially a catch block you can think around our whole request response pipeline that if an http exception or really any exception is thrown we can essentially route that quest to our exception filter and transform it into a standard error response so this is a broad summary of this request response pipeline and it should be noted we can directly inject any nest gas injectable dependencies into any stage of this request response pipeline so we can really do a lot with uh with this infrastructure with nest js so let's go ahead and jump into some code and see some examples of each of these stages okay so as always i've opened up a new terminal here and i'm going to generate a new nest project by using an scli we can run nest new and then i'll just use nest yes request response as the project name you can use whatever you'd like and then for the package manager feel free to choose whichever one i'm gonna use pnpm okay so we can go ahead and cd into our project here i'm gonna run p npm run start dev to start up our development server and it'll watch for changes here and with our server up and running we should be getting our standard uh localhost 3000 get request fire this off we get our hello world back from our server here in postman so i'm going to go ahead and open up vs code in this directory here and let's go ahead and dig in to this request response pipeline with some examples okay so we're going to start off with middleware that's the first place that our requests will be routed to middleware so let's start off with creating a new folder from middleware in our project here and like i said authentication um and attaching shared data to a request object is really a great use case for middleware so let's go ahead and create some sample authentication middleware here and so we'll have an injectable class because as i said before all of these classes that we're going to create can all be injectable and make use of the nest jsdi system so we're going to export a class called authentication middleware here which importantly will implement the nest middleware interface and from here we should get code completion to implement this use method here and the use method here we can actually change these types here so we know the request object is going to be coming from express as well as the response object here and in the next function we can actually provide it the next function from express so let's go ahead and also just add on the request and response here so in this middleware we have the request the response and the next function so we won't actually implement authentication here but i'll leave a comment to say here this is where we would normally authenticate the request and let's say we get back the user id right from our authentication service after we authenticate this request with a json web token on it now what i want to do is i want to make this user id here that we've obtained from our authentication i want to make it readily available to the rest of our application so that we can get access to it easily and we don't have to pull it off of the request object each time we want to use it so what i'm going to do i'm going to create a new service here called request.service.ts and what this service is going to be is essentially a container for all of our request-based data that we want to share in our app so what we can do is we can make this injectable and we'll export a class called request service now this request service i want to actually store the user id for my given request and then i want to essentially just add setters where we can provide a user id type string and then set it so nothing fancy here it's using setters and then i'm going to use a getter here where we return this.userid now in sjs as you probably know when we start our application this service here is going to be a singleton and it's going to be shared for the whole application which means that this right now isn't really application safe because if we have multiple requests coming in they're all going to be using the same service and this user id here might get overwritten so to get around this nest js offers us the ability to provide injection scopes which means that we can actually say that we want to scope this service to the request and that each request that comes into the system we'll get a new request service where we can set a unique user id for that given request essentially making our application thread safe or request safe so we'll have to go back into our app module and provide this request service as we normally would and now we can go into our authentication middleware we can actually inject it so we'll go ahead and inject the request service and now we can call this.requestservice.set user id and pass in our user id from our authentication now the lastly we're going to call next here to allow the request to continue so really what we've done here is we provided some contextual data for the rest of our application so now we can easily inject this request service elsewhere in our app and actually get this user id really easily now one last thing i want to do here is actually want to create a logger so we'll create a new logger here and this logger comes from nest js common and we can pass in the name of our authentication middleware class here and i just want to log out this middleware here just so we can see in the logs kind of the stages of this request response pipeline really easily we'll call this.logger.info.log and we will just provide the authentication middleware dot name here so we can just see that we're in this middleware so now if we were going to go into our app controller and in our default get hello route let's go into this app service and now let's go ahead and inject that same request service we just used in the middleware so typically it's a very common use case that in your service layer you want to get access to the current user on the request well now that we can easily do that using our request service we can simply call get the user id here by calling this dot request service dot get user id and now if we wanted to log out the user id we can actually copy over this logger here from our authentication middleware let's just change this name to app service and import to logger and now in our get hello we can call this.logger.log and we'll say get hello user id and let's pass in this user id so now before we can test this functionality out we need to register our middleware here so that our system knows about it now there really are two ways we can register middleware in an sjs app the first way is we could call in our main.ts here our bootstrap function we can call app.use and then we can pass in the middleware itself so we can pass new authentication middleware however this is a problem because we don't have access to the di container here so we'd have to pass in the request service so if we don't want to do it this way and we want to actually have access to the dependency injection let's open up our app.module and now in our app module we can say that app module implements nest module here which has a configure function and importantly this configure function here gets access to this middleware consumer and the middleware consumer we have a fluid interface that we can use to apply any number of middleware so here i'm going to call consumer.apply and you can see we have any number of middleware we can attach here so i'm going to call authenticationmiddleware and then we can call four routes and this four routes function here is very flexible allows us to pass in an object where we can specify the path so if we have a particular path that we want to provide this middle or for like path and then it can actually specified in method two so if we just want to use for example get requests this would be middleware that would only be applied to these routes but in our case we want this middleware to apply globally so we can just pass in a wildcard here so that applies for every single route so with our middleware now registered correctly if we open up postman and fire off a few requests here we should be able to go back to our app logs and we can correctly see here well when a request comes in we first see that authentication middleware log and then we can see our get hello user id properly outputting this user id here which we set inside of our middleware so next up in our request response pipeline is guards now i have a number of videos implementing real world authentication in sgs so if you'd like to see that be sure to check those out here we'll just implement a simple straightforward guard so let's open up our source directory i'll create a new guards folder in here and let's create a simple off guard that yes and as always this is an injectable here and we're going to export our class called auth guard now this is going to implement the can activate interface from sjs common and this function here is going to look like this so we have can activate which gets the execution contacts passed to it and we're going to return a promise boolean a boolean or an observable boolean so now instead of can activate we can do things like actually getting access to the request object very easily we can call context.switch.http.getrequest here so we've accessed the request object and we can actually do our authentication here with this request object like a json web token kind of approach however here we're just going to return true to say that we're authenticated and lastly and let's uh actually just copy over our logger here from our middleware and let's create a new logger in our off guard here so that we can log this out and see it working properly so make sure we import our logger from sgs common and then i want to log out this.logger.log the authguard.name here now to actually implement a guard in our system we kind of have three different approaches we can take so we can do the first approach here to attach it globally to our app in our main.ts as we saw with the middleware we can call app.use globalguards and we could pass in a new off guard here so now we fire off a request from postman and look at our logs we can see our authentication middleware and then our off guard is logged out here and so if we were going to go back to our guard here and return false so that all of our routes are now unauthenticated and we launched this request here we can see we get our 403 error because our guard is now forbidding access to our routes so this is one way to implement the auth card however if you have dependencies injected like our middleware did this won't work too well so what we can do instead is we can actually apply guards at the controller level so for an individual route we can call use guards here and then pass in our off guard lastly if we still wanted to apply this guard globally but we had dependencies that we wanted to inject into it we can actually use this guard in our providers away right we can call provide here and then use app guard and then call use class and provide the auth guard so now we're actually providing a global off guard here so if we call this request one more time we still see we're forbidden here and in our logs we can clearly see the auth guard getting executed so we can see our guard works fine i'm going to return true here again so that we should be able to now send requests and we can see our request response cycle looks good for now okay so we've looked at middleware guards now next is interceptors which are my favorite because of how powerful they really are so let's create a new interceptors folder and let's create a logging interceptor that we can use in our system to get some visibility around our request so i'm going to create a new logging interceptor here and as always this will be a new injectable class so we'll create a logging interceptor here which will implement the nest interceptor interface which will have this intercept function here now since this is our logging interceptor i'm going to copy over a logger here and create a new one this logger should be imported from sjs common and we're going to pass in the logging interceptor.name here to give it some context and we can see in our intercept function here we get access to the execution context just like the guard and then we get access to the next function here which interestingly will return us an observable which we can use to pipe our xgs operators on so what i want to do in this interceptor is i want to log some information about our current request so firstly let's get access to the request as we've seen before we can do this easily with the execution context by calling switch to http dot get request now let's get some information off of the request first let's grab the user agent so i'm going to create a user agent const here you can call request.getuseragent and we'll provide a default value of an empty string next i want to pull the ip address the route method and the url here off of the request object now we have all this information i want to call this.logger.log and i want to pass in the method the url the user agent the ip address here and i'll put a colon and now i want to actually give some information about the function we're calling so i'm going to call context.getclass which is the type of the controller class that this current handler belongs to so we can actually see where in our code we're calling when this request comes in so i'll pass in the name here of the class and we can go even further and provide the context.gethand so the actual function that's getting called we'll pass in the name here as well so after all that i'll just provide a message here saying this has been invoked now what i want to do is actually want to log out the user id for this current request so we can see who's calling our api now if you remember we have access to this in our request service and since the middleware runs first before the interceptor we actually have our user id set properly so we can easily inject the request service here we'll inject the request service and now we can do this dot logger dot debug so write a debug message now and say user id is equal to this dot request service dot get user id so now for each request we can see who's actually calling our system and so now let's actually implement some functionality to see how long our request took so i'm going to create a new date string here or date number so we can see uh before the request is executed and then we're going to call next.handle which essentially will execute our route handler and then we can pipe our xjs operators onto this so we're going to pipe tap onto it tap is just a side effect operator which will allow us to actually get access to the response of the route handler so whatever gets returned from the route handler will be outputted here in the response and now we can do things like actually getting the http response object so let's do that by calling context.switch.h and getting the response so the same way we got the request we'll do it to get the response and now remember this is after the route handler is called that this tap operator will be executed so we can now pull off the status code off of the response because it's been executed already and we have access to it let's also see if we can get access to the content length if it's applicable so localresponse.getcontentlength and then we'll finally log out a message here so we'll say the method just so we get some context again we'll method url and now we can specify the status code of this call to see if it's succeeded or not pass in the content length and then we'll pass in the user agent again and the ip so we can map this call and then what we're going to do to see how long the request took we'll call it date dot now minus the original now we created up here and this will tell us how long the request actually took in milliseconds so we'll provide ms at the end there and if we wanted to go even further here to actually debug what got returned from our route handler here we can call this the logger.debug and specify the response object here and pass that in so now we can actually see what gets returned from our api for each call so to register this interceptor our options are essentially the same exact approach as the guard we can call use global interceptor here in our main.ts and pass in the new logging interceptor but remember this is outside of the di container so we can't inject dependencies automatically here we'd have to do that ourselves so to get around that we can provide this interceptor at the controller level like we did with the guard by calling use interceptor here and then passing in the logging interceptor so this would work perfectly fine if you wanted to use an interceptor on a wrap by route basis and we can actually see this in action if we send this request off to our server you can see uh our logs actually have a lot of good information in them now so firstly our middleware is called then our guard and then we can see our logging interceptor here so we can see the the method here get it's a root request here and you can see the user agent so we're using postman and our ip address we can see the controller that's getting called the route handler pretty cool stuff and then in an interceptor we can see the user id that's currently calling this api we can see the app service running and then finally in the response section of our interceptor so after the handler gets called after the app service here we can see uh our response here and how long it took so this took one millisecond and we can actually see the response that got returned from the route handler which in this case was hello world so really powerful insight into our system here now finally if you wanted to apply this interceptor to every single route in the system which we probably want to do with a logging interceptor we can open our app module and do the same approach to the guard here we can provide app interceptor and then we can call use class logging interceptor now importantly if we do this approach we also need to provide the scope here and say that the request scope here for the interceptor because in this interceptor we're injecting a request scoped dependency so we also want the interceptor to be request scoped as well so now if we fire off a few requests we can again see our logging being outputted again okay so the next we're going to look at pipes in the request response pipeline so let's go ahead and create a new pipes folder and if you remember pipes actually just transform our incoming data into our system into any form that we'd like to work with really so allows us a lot of flexibility in both transforming data and also validating incoming data so we're going to do is we're going to create a new pipe called freeze pipe and all it's going to do is it's going to freeze whatever inputs coming into our system so that we don't accidentally change it in any way and if we do an error will get thrown so let's go ahead and create a new injectable class and we'll call this freeze pipe which will implement pipe transform so we're going to have a transform function here that we have to implement now we get access to the argument metadata here to learn more about the type that was getting passed in it can either be of type body parameter or query so here we really don't even need that all we want to do is simply call object.freeze and pass in the existing value so what this is going to do is it's going to prevent any modification of existing properties or the addition of new properties which could be useful in some cases so let's go ahead and return this so essentially we're making the object completely immutable and finally let's open up one of our other middleware here so we can copy over the logger and paste this logger in here so we'll output the logger and change this to freezepipe.name so now in transform i want to call this.logger.debug and say freeze pipe running so we can see in our logs our pipe working properly now as always we have different options for implementing this pipe we can make this a global pipe in our bootstrap by calling use global pipes and then passing in a freeze pipe now in our case this would work fine because we have no dependencies to inject but if you did a better approach would be to open your app module and we can simply provide an app pipe here and give it a used class so we'll go ahead and provide the freeze pipe here so this would work very well if we wanted to apply pipe to all parts of our application as well as inject dependencies however we're going to go ahead and remove this and we're actually going to implement this pipe on a route basis so we'll see how that looks if we open up our app controller let's go ahead and create a new post route here we'll call example post so this will take a body and we'll say this body will be a body of type any and now what i want to do is i want to freeze this body here using our pipe so what we do is we can essentially just pass in our pipe into the body here if we want only want it to apply to the body we can pass in any number of pipes into the body call and now if we were to call body dot test for example and try to set a property let's go ahead and see what happens so i have postman up here and i will post to our api here i'll just provide any body so we'll have a key set it's one two three if we send this we see we get an internal server error and if we go to cy we get an error so we can see our freeze pipe has run correctly and we can see this error cannot add property test object is not extensible which is what we'd expect because we've frozen this so this is great gives us a lot of flexibility however say for example we had a lot of different arguments passed in here some query parameters or what have you we could also call use guards here and pass in the freeze pipe directly and this way the guard gets applied with all of the arguments here in our call so the final stage of a request response pipeline are exception filters which if you recall essentially catch any errors in the system that are thrown and allow us to transform that error and do some handling with it if we so choose so let's go ahead and create a new filters directory and instead of filter directory we'll go ahead and create an http exception filter so we're going to decorate this class with a catch decorator here and this is where we specify the type of exception that we want to catch so if we want to catch all http exceptions we can provide the http exception decorator here and importantly if we want to catch every single exception that's thrown regardless of the type we could just leave this empty and not provide one but i'm going to provide an http exception because these are the errors we want to catch and transport into a different response so i'm going to create the http exception filter and this is going to implement the exception filter from an sgs common so we're going to have to go implement the catch method here okay and the exception here we have type http exception from sjs common and then we get access to the the arguments that were passed to the handler originally so we're going to go ahead and get the current context which we know of course will be host dot switch to http because this is an http exception filter and then from there we can actually pull out the response request and status so this is how we go ahead and we're just going to pull them off of this context called get response get request and get status and get status we can pull off the exception itself so let's also go ahead and import request response from express here so now that we have all this information about the error let's go ahead and use it actually so we're going to call response.status and send the original status in so this won't change but then we're going to change our json body here so we're going to pass in a new object we're going to pass in a status code property specify the status and then we're going to pass in a time stamp so we know when this error or exception was thrown we'll call two iso string here and lastly we'll provide the request.url here so we can provide the path of this failing api now just like interceptors if we want to register this http exception filter we can do it in three ways first of all in our bootstrap method here we can call app dot use global filters and call new http exception filter and this will actually work nicely for us because we don't really have any dependencies so that will work just fine however if you didn't have dependencies and you don't want to register in a bootstrap here we can go ahead and open up our app.module and we can again provide an app filter here and also use use class and then provide the http exception filter and then we can get access to dependencies injected inside of that filter lastly it's good to know we can also get access to the use filters decorator here and apply filters on a per route basis so we're going to get rid of this and just stick with the one that we've included in our app module so lastly let's just pull an existing logger out of one of our classes and let's add it to our exception filter so that we can actually see our exception filter in action so we'll go ahead and import the exception filter and then we'll change this to the http exception filter.name and then inside the catch block we're going to log out log out a log here and provide http exception filter dot name so we can see they were inside of this code so i'm going to open up our app.controller and i'm going to quickly create a new route here that will specify error and this method will just simply throw an error so we can see what happens with our exception filter in practice so we're going to throw a new internal server error exception here so now if we open up postman and we send the get request off to the error route here and send this we should see our json object here that we sent back from our http exception filter with the status code of 500 the time stamp when this occurred and then the current error path that we were on and also if we look at our logs here we can see that as soon as we invoked throw error handler here we can see the next log we get is in the logging interceptor for the user id and then directly in the http exception filter so you can see it working properly so this covers the entire requested response pipeline in sgs with some examples i hope this was useful for you and shows you the power of making use of nest chests and all of these different stages of the pipeline thanks for watching and i'll see you in the next one
Info
Channel: Michael Guay
Views: 42,088
Rating: undefined out of 5
Keywords:
Id: x1W3FJ1RJlM
Channel Id: undefined
Length: 33min 38sec (2018 seconds)
Published: Fri Mar 11 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.