NestJs - Best Practices + Implementations

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to be covering some of the best practices to follow in njs so let's get started using pipes and dto for input validation anytime your route Handler is expecting some payload from a request from a client you should always make sure to validate that request Data before actually entering the route Handler so for example here we have a body that we are expecting from a request we should make sure that this body object is sent in a correct manner so to do that we should use dto and pipes first of all dto or data transfer object is basically a class with some properties and it's used to specify the shape of the data that we are transferring in and out just by looking at this we can tell that the product coming from the body has the product dto shape so name price and description makes it more readable and cleaner it also helps us with type safety so if you take a look at create product it expects a new product of type product dto so the data sent should have this shape if you try and send something else for example h10 we are going to receive an error age does not exist in type product dto so using dto is going to help us avoid errors and ensure type safety in our code using a dto on its own is not enough to actually validate the request data so here if we try to send a product that does not respect that shape it's not going to throw an error because we need to use a pipe so a pipe has two different use cases transforming and validating input and the input is the one that comes from the request so from the route Handler parameters so such as body param and query in this case Body we are expecting a product so we need to register the validation pipe which is a buil-in pipe by njs so you can import it from njs common and all you have to do is just register it now in our case we're going to register it in main.ts and we're going to make it a global pipe so app. use Global pipes and then we create a new instance of that that pipe and what this does is it takes a look at the dto that we give to our route Handler params such as the body part here and it's going to look at those decorators that you can see here those decorators are from the class validator uh package and their role is to specify what the pipe should expect so for example here price should be an integer and should be between 1 and 100 including 1 and 100 because we're using the Min and Max constraint from the class validator package again so do not confuse the type that we use here number which is for typescript and The Decorator that we using here is in which is for this validation pipe that you are using here now that we have registered our validation pipe on a global level and we are using dto with the class validator if you go ahead and try to make a request using Postman we got an error from the pipe bad request and that's because we did not respect the dto shape so some other key was sent but it does not exist inside of our dto now the reason why it did not show more detailed messages is because I'm using disabled error messages so now if I remove it and hit send as you can see property some other key should not exist now this is good if you're in development environment but for production it's always a good or a better way not to show the exact message so you could maybe use some Dynamic code here to check your environment and based on that you set this variable I'm also using Whit list and forbid non-white listed so that anytime you try to send something that does not exist inside of our product dto so it doesn't have a decorator specifying the type or anything else it's going to throw an error CU by default if you don't have any of those it is going to let some other key pass in within your validation I do not really want that it's also important to know that for everything to work correctly with the validation pipe you need to install class validator of course of course we've seen The Decorator that this provided for our dto but you also need to install the class Transformer package if for any reason you didn't want to use the default validation pipe provided by nestjs and actually wanted to use a package such as joy for example you can still use it with pipes to validate the data so to create your own custom pipe all you have to do is use an injectable decorator and then implement the pipe transform and implement the transform method provided by the pipe transform interface and then here I have some code that I explained in more details in the pipes video so I'm going to keep the link down below but this way you can use Joy instead of the actual default pipe for your validations best practice number two logging it's always a good practice to log what's happening in your application whether it's on development for debugging purposes to catch bugs and understand how your program is behaving or on production to keep track of the events and to keep an eye on what's happening on your server nestjs provides a us with a default logger that we can use from nestjs / common so we can simply just create a new instance of that logger and then pass a context to it so a context string could be anything if you take a look at the startup logs we can see Nest Factory as a context instance loader and so on so you can choose whatever context you want your logger to have in this case I chose response for example and you can also create your own custom uh logger you don't have to use the default nestjs logger all you have to do is implement the logger service interface and once you implement that interface you would also need to implement log method error War debug verbos and so on now debug and verbos and set log levels are optional but you would need to implement those different methods of the log service interface meaning you can also use winant which is a very popular nodejs package to log but of course you would need to make sure that you implement the correct interface just like we said now that you understand the importance of of logging let's see what we have here so I typically like to log every request made to my server and then the response time it took for that request to be served now you can use a middleware or you can use an Interceptor if you want to use a middleware like my case all you have to do is create create a class logging middleware for example and then implement the nest middleware interface we should also not forget to use the injectable decorator on top of that class you also need to implement the use method which takes the request and response object and then the next function and what I'm doing in this case is I'm extracting the method and the URL from the request and then I'm taking the request time so the current time in which the request entered that middleware and then on my response object I'm listening to the Finish event so once the the response is over what I'm doing is I'm taking the status code and then I'm taking the current time in which the response is over and then I'm checking the status code if it's successful so 2011 or 200 in that case I'm going to lock some information about the request and response so for example the method of the request and the URL that way I can tell which API was called and then the status code of the response and the response time so how long did it take for my server to respond to that request that way we can track two important events which apis are being called more often and which requests are taking a long time to be processed on my server now if we want to register this logging middleware globally which is the case because we wanted to be logging every single request in every module that we have what we can do is go to app module and then implement the nextest module interface which will provide us with the configure method which takes a consumer of type middleware uh consumer and we can apply the logging middleware for routes and then we can pass in the Wild Card meaning all the routes in our application now that we have this in place if you go to postman and hit send and this time the body is correct if you take a look at the logs we can see that we got a log here with a context response which is the one that we set here in our code and then we got post which is the method we got the URL which is/ products and then we also got the status code 2011 meaning created and then we have the response time so it took 6 millisecond for this request to be processed just like we can log our successfully processed requests we can also and it is also a good idea to log our errors which brings us to best practice number three error handling just like we can use pipe to handle and process different validation checks on our request payload such as the body so the parameters of a route Handler like we've seen in point one we can also use filters to process and handle exceptions thrown from our application so by default NES comes with a buil-in exception layer which is responsible for processing and handling all unhandled errors in our code so anytime we don't have a TR catch block and we're not manually handling those exceptions they will be caught by the built-in Global exception filter and this exception filter will return the appropriate response based on the HTTP exception thrown so if your code throws a bad request exception it's going to return 400 with an error or a message saying uh bad request if you throw not found as an exception then it would return 404 as a status code and then the message would be not found and so on and if it doesn't actually receive an exception that it understands or recognize it would return this default message status code 500 and message internal server error but what if we could actually have manual control and override the behavior of this exception filter and we can create our own custom exception filter and use it as a global error Handler in our code and this would be a very good practice because in this way we actually have full control over that layer we can log the error that we are catching in it and we can also return a consistent error format to our client we can do anything we want here to create our own customer HTP exception filter all we have to do is implement the exception filter interface and then implement the catch method which takes an an exception of type HTTP exception and a host and if you realize here we are also adding the catch decorator and we specifying what type of exception we're trying to catch it takes an array of exceptions in our case we are uh we only care about HTTP exception here so the first thing I did is I created a new instance of the default njs logger but this time without a context and then here inside of our catch method we can do anything we want basically in my opinion it's a good practice to log the error that we get and to have a consistent format for the errors that we return to our client I'm not going to go in details uh and cover every line I have a dedicated video covering this topic so I'm going to leave the link down in the description but basically what I like to do is I like to return a flag error true and then the error details that I get from the exception you can do anything you want here now to register our filter globally just like we did for the validation pipe we're going to use app. Global filters and then pass a new instance of our custom new HTTP exception filter and to see it an action I'm going to send a payload from my request body which does not respect this product dto which will lead to this validation pipe into throwing a bad request error which will be CAU from our HTP exception filter so let's see what happen Happ s if I pass in a price that does not respect the 1 12 100 constraint so let's say I pass minus 10 for example and I hit send as you can see we got this error format now error true and then error details which contains the details of our error and this is because inside of our HTTP exception again I am the one who specified how the error should be returned I could have added the time stamp and anything else you might find useful in your application we can also take take a look at the logs and see that we successfully logged the error so we can see post SL products 400 which is the status code and then the error bad request which is the message best practice number four using guards for authentication and authorization now for authentication you can probably get away with using a middleware but in my opinion it's better to use a guard if you want to protect your apis for example you need to have an API that only certain users can access based on a JWT token for example or a certain role then this is when you should be creating your own authentication guard or authorization guard or both to create a guard all you have to do is create a class that implements the can activate interface which provides the can activate method that you need to implement which takes a context and then this can activate method is going to return a bullion or if you take a look here it can also return a promise of a bullion if you have a synchronous calls or an observable of that returns Aion such as if you're using rxjs for example now if the Boolean return is true then you have given access to the request or to the user making the request and the request would go to the correct roud Handler just like it should but if you return false then this is going to throw a forbidden exception error meaning 403 as a status code now in my opinion in an authentication guard we should throw unauthorized exception instead of forbidden because unauthorized meaning status Cod for1 is usually for wrong credentials or the identity of the user we have problems identifying the users or something but forbidden is used whenever we know who the user is but they do not have enough permissions to access a certain route so they are unauthorized to enter a certain route I explained the differences between uh authentication and authorization in a dedicated video I'm going to leave the link down below where you can also watch uh how how we created the guard uh in details because here I'm going to go uh in a faster way so basically we got the request and then we are checking to see if we have a token in our request headers if you don't have a token we're going to throw new unauthorized exception so the user does not have access to the route Handler and then if you do have a token we're going to call a method called verify token which is going to decode and verify that token using our secret key which would be in an environment variable and then let's say uh it worked successfully we're going to return the decoded token usually it's going to contain the user ID for example so here I'm going to attach a user object to the request which is going to contain the decoded uh payload and then we're going to return through meaning we have given access to the user so the request will go to the rout Handler successfully again in case you want to see a fully detailed and fully functioning authentication guard with a full code then in which we use the actual JWT verify method using this Json web token package which is very popular to decode and verify a token in which we also check different configurations and we use the JWT module in our application please check out the video in the description where we cover it fully now to use our authentication guard I registered it using use guards on top of this route Handler and then we are using the rec decorator to get the request but I'm only extracting the user part of it which is found here request. user which contains the decoded token and then I am logging the actual user so it should log id1 to3 unless it throws unauthorized now in case we send a token if I hit send as you can see in the logs we got 1 to 3 everything was fine but if we do not pass a token let's see what happens if I hit send as you can see we got error true and error details with a method unauthorized so we can see our exception filter once again in action catching the errors and then turning a consistent error whether it's an error thrown from our oard or from our pipe or anywhere from our code so we can see that we have a clean separation of concern in our code our pipe is only validating uh the request payload our exception filter only cares about handling the errors and our guards only care about validating uh the token authorizing a user and so on and finally best practice number five using and creating your own custom decorators so we've seen in SGS we use a lot of decorators built-in decorators such as ADD module uh add controller so we can pass in different options we can modify a behavior we can pass some metadata and so on just like you can use buil-in decorators you can also create your own custom decorators a good example for that would be creating our own custom roles decorator to specify which roles can access which routes so just like we can specify that this route Handler uh accepts uh requests of type HTTP post here we can also specify that this route only can be accessed by users who have the role of admin creating a decorator is simple basically we need to call the set metadata method which returns a custom decorator we need to pass two different arguments the metadata key and then the metadata value so the metadata key is the key for this decorator for example roles to refer to that decorator and then the metadata value what value should this decorator return here it should be dynamic based on whatever we pass in so if you take a look here we want to pass in an array of roles so our function needs to take an argument which is roles which is an array of strings it could be an array of enome or anything based on your business logic or business rules in this case this is how our decorator would look like now let's see how we can access this secator in our authorization guard so if you noticed here in use guards after our request goes through the authentication guard if everything is correct and there's no errors thrown before entering the route Handler it should also go to the authorization guard since it exists in the use guards array so inside of our authorization guard again this is a template but if you want to go more in details you can watch the video down in the description basically what we're doing is again we're creating a guard this time it's called authorization guard because we're handling authorization we could also call it roll guard since we're looking at the RO here now to be able to get this rols decorator and get the metadata meaning the value so the array containing admin we should use a reflector so we can inject it here so we're doing dependency injection here and Reflector by the way is from njs score I'm not going to go too much into details but basically here we are getting some metadata using this reflector from a Target which is the gut Handler because we're using this decorator on top of the uh of a route Handler basically which is create product in this case and then those would be the required roles for this route so whatever we get here is the required rules if you don't have any required roles then we can return through because there's no rule specified for this route and then I'm going to log those roles again I'm going to get the request and this time I'm taking the user from the request. user now don't forget request. user has been assigned here in our authentication guard before so anytime we want to call our authorization guard we need to make sure that we are applying our authentication guards first just like we did here use guards we specify the authentication guard and then the authorization guard what we're doing here is I'm getting the user if you don't have a user then something is wrong we probably didn't go inside of our authentication guard so I'm throwing an error auth unauthorized except setion and then what we need to do is get the actual user role from our database so here I made a function it would need to have a logic and then it would end up returning some role from your database based on the user ID that you have from the user from the request. user that we attached earlier in the authentication guard and then using this user role you can compare it to the required roles coming from the metadata from the custom decorator that way you can tell if that user had the correct roll and has access to that route or not let's go ahead and see that in action if I hit send take a look at our logs as you can see first of all we got inside authentication and then we got inside authorization so the authentication passed and then inside of authorization we are logging the required roles so the metadata from the roles decorator which is here on top of the route Handler it said admin and then we logged the actual user ID that we have or the user object that we have on the request which is id1 to three and then we have printed the actual user Ro from the database which is admin from here since it matches the route R we can see that we got a successful response now let's say we returned user for example so the user role in the database is user and not admin if you hit send as you can see we got an error here status code 403 meaning forbidden and that's the error here and the message is user does not have the required roles which is the message that we have here inside of our authorization guard so now you can actually see how everything is working smoothly we have separation of concern we are using guards to authorize and authenticate a user we are using pipes to validate the request payload data from a bu body from a query or anything and we are implementing logging so anytime we have an error we're actually logging that error and we are also implementing logging for every response so anytime we make a request we are able to see what request was made and then the response time it took and you have also seen how we can return a consistent error format to our client those are some of the best practices that you can follow in sjs to keep your project clean and be on a good track of course there are more like dependency injection keeping your code modular decoupling and so on if this video gets a lot of interactions likes and Views then I might make a part two so make sure to share like And subscribe thank you for watching and I will see you next time
Info
Channel: Computerix
Views: 3,241
Rating: undefined out of 5
Keywords: nestjs, nestj course, nestjs tutorial, best practices, nest.js, nest js
Id: LeYbsTzOctA
Channel Id: undefined
Length: 22min 25sec (1345 seconds)
Published: Sun Feb 11 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.