Build a RESTful API with Node.js, Express & TypeScript

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
if you're a junior to mid-level developer looking to build rest apis like a senior stick around because in this video you're going to learn how to do just that in this tutorial we're going to build a rest api with node.js typescript mongodb with mongoose and express we're going to cover some theory along the way and we're going to be doing a whole lot of practical coding so who is this tutorial for this tutorial is for any junior to mid-level developer or anyone who wants to build out rest apis with typescript so why would you want to build a rest api in 2021 well rest apis are still the staple of the web development industry knowing how to build rest apis is important for both backend and front-end developers and there's no better way to learn than to build one so the concepts that we're going to cover are rest api principles so we're going to have a look at crud http methods what a rest api is and is not we're going to look at jwts and refresh tokens the technologies we're going to use to build the api is node.js mongodb with mongoose typescript express and express middleware so we're going to start off by having a look at what it is we're going to build we're then going to bootstrap the application we're going to build out the registration and we're going to build out the crud operations for our post objects so we're over here in postman and we have a rest api tutorial postman collection and the first end point is to create a user we can hit this endpoint here and we get our user back the next endpoint is to create a session and this is to log the user in so we send along our email and password and we get back an access token and a refresh token we'll take this access token and put it into our environment variables and we'll do the same with the refresh token and if we hit this get sessions endpoint we can see all the sessions that this currently logged in user has we can see that the time that they logged in the user agent that they logged in from and this array will show you how many sessions that user has and whether the session is valid or not we can create a post and after we create a post we get a post id and we can set the post id in our environment variables we can update our post and finally we can delete our post so there's a few different ways to structure a rest api some people will structure it via the resources so they will group the controller the model and the service all together for a resource i like to group the folders via what they do so i like to group all the controllers together or the middleware together or the models and all the services together i just think that this is a much more consistent way of grouping our applications logic you can see here that http requests will come in via an endpoint the request will get passed off to some middleware and then the request will come back through the controller down to the service and down to the database and then the request will come back all the way up and return back to the client you can see in our folder here that we have some controllers so we have a controller for our posts sessions and our users and we have a model group to match that so we have models for posts sessions and users and then same for services we have services for post sessions and users so without any further ado let's get started bootstrapping our application we're going to start with yarn in it to initialize an empty project they're going to use mpx typescript in it to initialize a typescript project and we're going to install some dependencies you can find these dependencies in the notes section of the finished repository we're going to install some development dependencies again you can find these in the notes section of the finished repository next thing we're going to do is to make a source directory and we're going to make an app.ts inside the source directory then we're going to configure nodemon to start our application and restart every time we make a change so touch nodemon.json and put the following script and come over to app.ts and we can simply console logout message that says hello world and we can put a script into our package.json to run nodemon which will run our app.ts run yarndev and you can see our application prints out hello world and it waits for restarts if we come in here and save it we should see the application restart and we should see hello world get printed to the console again so we know that nodemon is working now the next thing we'll do is go through and create the structure for our application so we'll make a new directory in the root directory called config and then inside our source directory we'll make a new directory called controller we'll make a new directory called db we'll make a new directory called service and we'll make a new directory called model so come into the config folder and make a new file called default.ts and this is going to be our applications config so we need to export a default function and we're going to set port to port 1337 you can set this to anything you like we're going to set host to localhost we're going to set our database connection uri and that's everything we're going to set up for now i'm into app.ts and we can begin by importing express from express we'll import config from config we're going to get our port and host variables from the default config with config.get and then the name of the variable we're going to import port as a number and host as a string so we can create our express application by calling a new instance of express because we're using a newer version of express we don't need to import body pass or middleware express comes with the ability to pass json so we can call app.use express.json and this app.use is middleware that's going to be used on all routes and finally we can say app.listen on the port that we specify on our host we're just going to console log this message server listening on our host and port so we can run yarn dev again and we should expect to see this message get printed to the console and we can see that our express application is now listening on port 13337 the next thing we're going to do is to set up our logging service so make a new folder called logger inside the source directory and inside logger create an index.ts so we'll be able to import our logger from this logger folder and this will contain the formats for our login so we're going to import logger from pino and then we're going to import ajs from djs and then we're going to format our logger with pretty print true we're going to omit the process id from the log and we're going to format the timestamp with day.js so we can save that we can come back into our app.here and import the logger and we can replace our console log with log.info and you can see here that we've logged the timestamp with our message of level info so there's two main reasons that you would use a logging service like pino or winston over console log and the first reason is that you can format the messages like this and the second reason is that console.log locks the i o and because node.js is a single threaded application you do not want to be blocking the i o because it will slow up your application the next thing we need to do is to configure our database connection so come into this dv folder and create a new file called connect.ts inside connect.ts we're going to import mongoose from mongoose we're going to info import our config package again and we're also going to import our new logger we're going to get the db uri from config and we're going to get that as a string then we're going to create a function called connect and we're going to export that function as default and inside the function we're going to call mongoose.connect we're going to connect to our db uri and we're going to pass it these options here and mongoose.connect returns a promise and so we can call.then where we'll log out our database connected and we'll call dot catch if the database connection fails and if the database connection fails we also want to call process dot exit and we'll call that with 1. this will exit the process with a failure so if we come back to app.ts we can import connect from db slash connect and once our server has started we want to call connect and we should see our service restart here and we should see a message to say that we've successfully connected to a mongoose database so mongodb database you can see here database connect the next thing to do is to set up our routes so we'll create a new folder new file so in the source directory or routes dot ts and our routes file is just going to export a function and this function is going to take an instance of our app and the instance has a type of express and we can import express from express so when we import express as a named export and with a capital e then this will refer to the interface and we can see that as well with our request interface and our response interface and if you command or control click those you can go in here and you can see what this interface looks like so if you want to see what a request objects look like you can command click it and then you can start exploring in here and this is a really handy feature if you want to see what an interface looks like and what properties can be passed in there so we're just going to have one route in here so we're going to say app.get and we're going to make this a health check route just so we can make sure that our application is working and our callback will take two properties one of request and one of response and we can type these with our request and response interfaces that we imported from express and we're just going to res.send status 200. until if our application is up and running we should expect to see a 200 response from our health check endpoint the 200 response in http is ok which means our application is good and the response is what we would expect we can open a new terminal here and we can send a curl request to http localhost slash port one two three seven slash health check and we can see here that we get a cannot get slash health check and that is because our app has no idea that these routes exist we need to import these routes and we need to execute them while passing in our instance of our application so once the database is connected we can also initialize our routes and we pass in our app we can send this curl request again and you can see that here we get ok which means our application is up and responding to requests let's start building out our user registration and login process so we're going to have a few endpoints here we're going to have one to register the user we're going to have one to log in we're also going to have the route to get the users sessions and we're also going to have a route to log out because rest performs crud operations and uses http methods we can't have an endpoint here that says something like slash api register user because this here is an action but our action has to be post so what is it that we're actually creating here well we're creating a user so our route is going to be post slash api slash the resource that we're going to create and in this case it's a user when a user logs in again we can't have a slash post slash login because login is the action but so is post this here is an ace valid rest api route what we're actually creating here is a session so this will be post slash api session and to get the user's sessions you've guessed it we're going to get such api sessions and to log out what we're actually doing is deleting a user session so it's going to be a delete method api slash sessions and this here should be sessions as well so you'll notice that all the routes have the resource as plural and that's because when we have the routes that are get api slash posts this will return all the posts and as an extension to this route we want to return one post so that api route will be slash api posts slash post id so if we had posts here but post here this would tell the user that these two things are different resources they're not they're the same so the convention is to use plural for the resource in the route so now that we know the rest api routes that we need for our user registration let's come over to our controller and create a user.controller.ts and i like to put the word controller in the name same as services and models just so when they're in the tab you can see what it is because you'll see here that we have a user.model.ts and if they're both called both called user you wouldn't know which one is the model and which one is the controller and again for service we're going to have user.service.ts let's start off with our user model a user model is going to have three imports it's going to import mongoose decrypt and config and so mongoose is a orm for mongodb decrypt is for creating hashes because we're not going to store our passwords in plain text we're going to store a hash of our password and config is to get config from the config folder so to create a schema we're going to call mongo's new mongoose schema and then we're going to put in an object of what we want this schema to look like and we're also going to put in some options here and in this case we want time stamps true so this is going to give us an automatic created and updated date we can then create a new user variable here which is going to be a mongoose model that takes a schema and we're going to name it user and then we just export user as default but because we're using typescript we also want to create a interface for our user so we can create a new interface here called user document that extends mongoose document and this is going to have all the properties that a mongoose document would such as the underscore id and version and everything and it's going to also have our custom properties that we've defined in our schema so to make use of this schema we want to export our model and if we command click on this model we can see that here we get a generic type that extends document so if we don't pass anything here we're just going to get an empty schema that extends document but if we pass our own interface here we'll now get type definitions that include our custom properties on the schema and you'll notice the schema here has a compare password method so we're going to build out that compare password method now so what this method does is it takes a candidate password it looks for the user in the document and in this case it's just assigning the variable user to this which is the user document and then it's going to call the bcrypt.compair function which is going to compare the candidate password with the user's password the user's password is going to be a hash and this candidate password is going to be a plain text string and so this is going to tell us if we can turn this plain text string into this password here and if we can then it will return true and if we can't we're going to return false but we need a way to get the user's password into a hash so we're going to call a user schema pre-save method you can see here that again we create our user from this and if the user's password has not been modified we'll just return next we'll create a salt and we need to put this salt work factor into our config because at the moment we're calling it here with config.get then we're going to create a hash and then we're going to swap out what the user gave us for the hash of their password and then we're going to continue and save the user let's add this saltwork factor to our config and what salt does is add a random number of parts to the password hash so it makes it harder for the passwords to be cracked come over to our user service and we'll create a few functions in here we create a function to create a user create a function to find a user and we'll import our mongoose model and document you can see our create user function here is going to take some input and we're going to call this document definition and if we command click on this again so if we import this and then we can command click on it so we're going to import document definition from on goose and we can see again this is a generic type so we can pass our user document into here this will give us some nice type definitions when we go to call create user we can see an example of that here so we say create user and we can see the properties here that we can pass into create user and we're going to try create the user and if it returns an error we're going to throw that error otherwise we're just going to return the new user and the reason this might throw an error is we have a unique condition on our email and so if the email has already been taken we're going to get a duplicate key error and we're going to be able to return that error back out to the user so before we continue let's go over to our user model and we can build out our create user handler so we're going to export a function called create user handler and so this is going to be export async function because we're going to be using async await in this function and we're going to create a try catch block again because we have that unique validation on the email we might get an error here we need to import create user from our user services and our controller handler is going to take two properties and that's going to be the request and response and again these types come from express so we can import those from up here and we're going to call our create user function with our request body and this request body here at the moment could contain anything so we need to validate this request body and we'll get to that with express middleware so if this function succeeds we're going to call res.send and we're going to emit the user's password from the response and this emit comes from lowdash and if you haven't used lowdash before it's just a utility library that contains lots of useful things and in this case we're going to use the omit function which takes an object and then an array of properties or a single property that you want to emit from that object the alternative to that is to call delete but you should generally try to avoid deleting because it manipulates a object and then if we encounter an error we're going to call res dot status 409 which is a conflict and we're going to send error.message the most likely scenario here is that somebody tries to register with an email address that's already taken and in that case it's going to be a conflict and we also want to log out the error here we're going to import log from our logger i'm just going to call log.e so the next thing to do is to build the handler for this route so this is going to be app.post slash users api slash users we're going to have this validate request middleware which we'll build out now and then we're going to call this create user handler which we need to import so we're going to start building out our middleware so create a new folder called middleware and we're going to create a new file called validaterequest.ps and to validate our request we're going to require a schema and then the function is going to take in the request response and the next function we're going to try validate that schema and if it succeeds we're going to call next and if it doesn't we're going to log the error and we're going to send a 400 and if you haven't seen this syntax before basically what we're saying is we're going to initialize this function here with one parameter but then we're not going to execute the function until we receive the rest of the parameters you can see here in our route we initialize the first part with this validate request call here and we pass in the user schema and then when the route gets hit we call this part here and this method is called currying and it comes from functional programming and it is very useful and definitely worth looking into so we're going to import validate request from our middleware and you can see here that we also have a schema that we need to define let's make a new folder for our schemas and we'll create our create user schema actually let's create user.schema.tiers and we'll like our services and controllers we'll store everything to do with user in this one schema so you can see our create user schema here is a yup object so we're just importing object string and ref from yeah and we're going to create a new object here and our request is going to be the portion of our request that we want to validate is in the body that again this is another object and we want to have a name and if the name is not present in the request we're going to send this error here so it says name is required and then we're going to set some rules for our password so we can say that it needs to match this regex here which just says that it needs to include either letters or end numbers so it will only contain latin characters it needs to be at least six characters long and it's a required string and our password confirm needs to be the same as our password and if it's not then we're going to return this error here passwords must match and our email must be a valid email and again it's a required string we need to import our create user schema pass it into our validate request so once we've saved this and our server has restarted we can come back over to postman and i've deleted the user that i created earlier we can resend this request and we see we get a new user here so that's our first route complete the next one we're going to do is to log in if we create this route here we can see again that we have a validation on the request in this case it's going to be create user session schema and we're going to call the create user session handler let's import the handler from our controllers and we'll import the schema from our schemas and we'll go in and we'll create this handler again this is an async function that takes two parameters request and response there's a couple of things to note here all of our controllers are going to take the same parameters request and response and our controllers are never going to interact directly with our database that's what our services are for so this means that we can separate our database logic from our business logic and in this application it's quite simple your application might get more complex and you'll be thankful that you separated those two concerns so let's work out what we need to do in our create session handler so we're going to take an email address and password and we need to validate that the email is registered and the password matches that registered account we need to create a session we need to create an access token and we need to create a refresh token then we're going to send the refresh token and access token back to the user before we continue let's move this into a session controller because sessions end users are two different objects and this here is creating a session and not a user and because sessions uses are two different objects we also need a session service and a sessions model so you come in and create session.model.ts and that session model is going to look a lot like our user model so you can see here that we have our session document that again extends mongoose.document and then this time we're going to also import the user document because the user on the session is going to reference the user's id we could say here that this is a string but it doesn't really tell other developers where this string comes from where if we say this it tells other developers that our user refers to the id on our user document we're going to have a valid boolean we're going to store the user agent string and because we have this time stamps true we can also access created and updated at and our user is also going to reference a schema types dot object id and it's going to reference the user and again we're going to export our model with our session document and we're going to call it session and we're going to export it with our session schema and this here should be search so let's come back to our session controller and we can see here that we need a validate email and password so this looks like it should be a service we need to create a session again we need to create that in our services we need to create an access token and for that we need to sign an access token that doesn't really interact with our database so we're going to create that as a utility and the same as refresh token and then we're going to resend we're going to send the refresh an access token so let's create a service we're going to create the function to validate the user's password inside the user service you can see here that we have a function called validate password and it's going to take two properties an email and a password it's going to try find our user by the email address if it can't find the user it's just going to return false the email or password is incorrect and it's going to create this is valid variable which is going to be a boolean and it's going to call our user.compare and this is because on our user schema we created this compare password function one thing to note about this is that if we call dot lean on this user find we'll no longer have access to this user.compare password but we can't call lean here and if it isn't valid we're going to return false and if it is valid we're going to return the actual user but we're going to omit the password so let's import this admit from lodash again and this is our validate password function back to our session controller we can call our validate password service first we need to import it from the user service we're going to create a new variable here called user just remember our validate password function returned a user or false so we can say here if not user return a status of 401 with a message that says invalid username or password you can see we're getting some errors here because request and response hasn't been imported from express so we can import those at the top here and we should see those errors go away so the next thing we need to do is to create a session so if we come over to our session service we can create a function to create a session we also need to import our session session model and document and in our controller we can import our session create session service and to create our session we're going to pass in the user's id we're going to pass in the user agent and this function here will create a new session with our user id and our user agent we've saw before that our user agent was postman if we were to make that request from a browser our user agent would be something else say something about mozilla and it we'll make that if we were to make that request from a microservice we would set the user agent to the name of that microservice and its version and so we could log where requests were coming from so our next thing to do is to create an access token so let's come over to our session service and we're going to create a service in here to create an access token so you can see that our access token will a function will take two properties our user and the session that we created and we can import this user document from mongoose so we can import this from our user model we also need to import config from config and so this function here will expect a user with the password omitted from it or it will expect a lean document but again with the password emitted so this lean document means that we can pass a user in here that has been found with dot lean or a user that has been found without calling.lean so we need to import this lean document from mongoose and same with session we can either call this with a session that has been found without lean or with calling.lean on defined function and we can see here that we have this sign function that we need to create and this is going to sign our json web token and it's going to put this object in here and it's going to create it with this option here so this is a expires in and this is an access token time to live we're going to make this about 15 minutes we'll just make a comment here the reason this is so short is something that i will explain when we get to refresh tokens let's create this sign function here let's create a new folder called utils and we'll create a new file in here called jwt.utils.ts and we're going to create this sign function we need to import config from config we need to import jwt from our json web token package and we also need to create this private key let's create it as an empty string for now actually we'll create it as a config dot get and we'll put this in here this is going to throw an error for now but we will put our private key into our default config and we're going to allow some options here and so i got this type here by clicking on this json.sign and we can go into the type definitions here and we can see that it has a sign options and so i imported the sign options from jwt or it can be undefined and then this will give us some nice typings when we go to use this sign function so we now need to import our new sign function from our jwt utils and you can see here now that we're going to get a signed access token so this is our create access token function complete we can come back to our controller and now we can create our access token and we need to import our create access token from our session services and to create our refresh token we're just going to create our refresh token we're just going to call sign utility with our session object and we're going to make it expire in about a year a comment here that just says one year and we need to import config and our sign utility so you may be wondering why have i extracted this create access token out into a service but the refresh token is just created with our utility and that's because the utility is good enough to just be called directly with our session object where the create access token needs to have the two combined and a little bit of logic applied here so let's go to our config and we need to add these properties so we need to add an access token time to live we need to add our refresh token time to live and we also need to add our private key can see here that our access token time to live is 15 minutes and our refresh token time to live is going to be one gear and then if you go to google and type in generate private key you'll find a website where you can generate a private key and then you can copy and paste that into your config file but this private key i will upload to the repository so you can just copy this if you like but make sure you're not submitting your private keys to repositories and storing them online if they're going to be used to actually sign real refresh tokens and jwts because that is very insecure so we're back at our session controller and we've created our access token and our refresh token so now we're just going to return from our function here res.send and we're going to send the access token and the refresh token so you can see that our server is still crashing and this is because we don't have our validation schema set up you can see here our create user session schema is not exported from a user schema so let's come into our user's schema i'm going to create a session schema here so again we're validating the body of this request and we expect that to be a password with the same rules as our password up here and we expect there to be a valid email and our email to be required we can save this schema and before where we when we moved our session handler out into the session controller we didn't move the in we didn't move the import so we're going to import it from the session controller and you might be thinking there's a lot of back and forth between files and a lot of jumping around and that's true and it's true for the start of every application and as you build out more and more of these services you don't need to jump around and so once we've built out the jwt services for example we don't really need to open that file again same as the services files once we've built those out we can start reusing different components of those services so now our server has restarted successfully we can go back to postman and we can create a session and see if our handler works and you can see here that we get a new access token and a new refresh token but what does that actually mean what can we do with this access token and what does a refresh token do let's have a look at the so you may have noticed that our access token only has a time to live of 15 minutes let me quite annoying if you have to log in every 15 minutes but we also have no control over who is logged in so if we want to log a user out then we can't because so if we make our access tokens longer say a year we can't log those users out because our application is stateless so there's a problem we either need to make a request to the database every time the user makes a request or we have our application stateless or we use refresh tokens and so when the token has expired we would check to see if it's an expired token and then we'll check to see if the token was valid sorry we'll check to see if they have a valid refresh token and if they do we'll reissue a new refresh token if we have a look on our session model you'll see a valid and we're going to check this valid to make sure it's still true so if we want to log a user out we can set this to false and it will invalidate their refresh token and when their access token expires they won't be able to get a new access token and they'll be logged out permanently so this gives us the best of both worlds we get stateless requests for 15 minutes and then we can validate the refresh token to let the user's session keep going so we're going to do all this with some express middleware so come into the middleware folder and create a new file called deserialize user and we're going to have a look at what this function does so we're going to get the access token out of the request headers so we're going to put this in headers.authorization and that's where access tokens always go we're going to have a custom header called x refresh and that's where our refresh token is going to be and if there is no access token we're just going to return next if there is an access token we're going to try to decode it and if we can decode it successfully if the access token was is used within that 15 minute window we're going to append the user to the request object with the decoded token and then we're just going to return next and this is where the refresh token logic comes in so if the token is expired and we can see that from this decode function that we will need to create and there's a refresh token we're going to call this reissue access token function which we'll also create and then we're going to see if the see if there is a new access token and if there is we're going to set it on a header so the user can reuse it and then we're going to decode the access token and then we're going to append the new body of the access token to the user object and this means that in our handlers we can call rec.user then we're just going to return next and so we can pass through this middleware so let's create our decode function in our jwt util so our decode function here is just a wrapper around jwt dot verify and it takes our token and our private key which we have set in config and it returns this object here so we're just going to say if it's valid we're going to say if it's expired in this case if it's valid it hasn't expired and we're going to return the decoded version of the token if there is an error we're going to say the valid is false we're going to set expire to true if the error message equals jwt expired and we're going to set decoded to null so if we come back to our deserialize function we can see that we also require a reissue access token in our session services you can see our re issue access token function here is going to try decode our refresh token and if we can't decode our refresh token then we're going to return false we're going to try find the session that is in the refresh token if the session is not valid again we're going to return false we're going to try find the user and then we're going to create a brand new access token so let's import decode and get and find user so decode comes from a jwt utils folder and you can see here that we're already starting to use reviews things that we've created before get comes from low dash again get is a very helpful utility function that will allow us to get properties without the risk of get is a very helpful utility function to allow us to get properties from functions without getting errors so if we try to get id from decoded and decode it is null then we'll get an error because you cannot get id from property undefined but you can also use the the knowledge coalition syntax so you could say decoded dot id but i prefer to use low dash because i like the syntax a little bit better but you can use what you like and we also need to import this find user method from our user service we also need to implement our find user function in our user service so again we're going to export an async function and query is going to be a type filter query and it's going to take our user document and then we're going to call user model dot find one and then pass aquarium and lean there's a special trick to creating these services you want to create them specific enough that they're useful but you also want to create them generic enough that you don't have to create a bunch of services that do roughly the same thing so i find this is a nice in between ground where we can pass in whatever the query is that we want but we're also not tied to one query so if we made a service for example that was find user by email and then we wanted to find a user by their name we would need to create a new service or refactor our existing service with this service here we can do both and we're going to get some nice helpful typescript helpers as well we also need to import the filter query generic type so now that we have our d serialized user middleware implementation done we need to create our logout route now log out route is quite simple all it's going to do is invalidate a user session so let's create this invalidate user session handler and we're going to import it from our session controller and again we're going to export an async function it takes two properties request and response and we need a service to update a session so we don't have one yet so we'll go and create that so our update function takes a query so this will be used to find the document that we want to update and then i'll take an update query so this will be the body of the update so come back to our session controller we can import our update service and you can see here that we're getting our session id from the request.user.session so we need to add this functionality so we need to import get import get from lodash and the reason that we can get our session id from our request.user object is because in our deserialize function you can see that we attached it to the request and this access token also includes a reference to this session id and then we call our update session service and then we're going to return a status of 200 because the user has been logged out successfully and you can see here that we're using this require user middleware but we haven't actually created this middleware yet so let's go ahead and do that come over to the middleware folder and create a new file called requireuser.ts and i'll show you a nice little import export trick here with javascript so we can create a index.ts and in our routes we have all these imports for middleware and we will have a new one here it would have three lines here but if we just want to import all of this as named functions from the middleware folder we can do that in here so we can say export and then create a named export as default as whatever we want to call it so we can call this one be serialized user and then where we want to get that function from so what this is going to do is import the default function from the serialize user and then it's also going to export it as a named export and we can do the same with require user and validate request and we haven't implemented the required users user middleware yet which we'll do now and so this is going to take a request response and next and if we compare this to our validate request function they look a little bit different you can see here that there is two functions here this is our schema and then the next function here takes our request response and next and in this case the first function here is going to take the request response and next and that's going to determine how we actually use this middleware so this is going to try get the user from the request object and if the user doesn't exist we're just going to send back a 403 otherwise we're going to return next the reason that user will exist in this is because we've called this deserialize user in fact we need to go into our app.ts and we need to call this d serialize user on all routes so if we come into app.ts we can import our middleware and we can say app.use and then pass in our deserialize user function so this will attach our user to every single request that comes into the application and i have tried to import update session from the user service now that we have our logout route complete let's go over to postman and test it out we need to log in we need to get our access token and update our postman's environment variables update the refresh token as well and we can try delete our user's session so we click res.send we get an okay if we have a look in our database and sorry i can't get robo 3t to zoom in so it is a little bit small but we can see here that our session is valid false because we've logged out so the last thing to do for our sessions is to get a list of the user's sessions and the reason that we would want to do this is because if the user wants to see where they've logged in and when they're logged in we'll be able to show that information and they'll be able to log and they'll be able to destroy sessions if they like so if they are logged in on a device that they don't recognize they'll be able to log out from that device so the handler for this is going to be slash api sessions and again we're going to require the user and we have a get user sessions handler and we have a get user sessions handler which we will now create we will import that from our sessions controller come over to our session controller and we'll create the handler you can see this handler is very simple it's going to get the user's id from request.id and we know this is going to exist because we've used the requires user middleware we're going to find the sessions that belong to this user and that are valid so we need to implement this find sessions service if we come over to our sessions.service we can implement this and again this function is going to be very simple it's going to take a query of type filter query and we're going to query the sessions model and we're going to call dot lean dot lean is going to return a smaller document without all the mongoose stuff attached there's some good documentation on lean on the mongoose documentation website and that is our get sessions endpoint complete so we'll come over to postman and test it out so if we send this request we can see that we are logged in once if we create a few more sessions we should see a few of these objects in the array and you can see that we've logged in a few times now so that's it for creating the user registration part the next part we're just going to be creating a post model and then we're going to be able to create posts update posts and read posts so come back to our routes and the first route we're going to create is to create a post and so it's going to be app.post slash api slash posts and we have two bits of middleware in here and so we put these middleware in an array and so we're going to require that the user is logged in and we're going to validate the request body and we're going to create a create posts handler the next route is going to be to update a post so this is going to be similar to the post to the create post route except we require a post id and the request is going to be a put request and we're going to have an update post schema and we're going to have an update post handler the next route we have is to get a single post and so we require a post id param and we're going to use the get post handler and we have no middleware for this route because we don't need the user to be logged in and there's no request body that we need to validate and the final route is to delete a single post so again we're going to require that the user is logged in and we're going to validate the post request body so let's create these schemas to validate these requests and then we'll create the handlers to create a post.schema.ts and we have a few validation schemes in here so we have one for creating a post updating a post and deleting a post the body for creating and updating a post is the same so we have abstracted this out into a different variable called payload and then we're just going to spread that payload into the body of the schema but to update a post we require that the param includes a post id and the same with deleting a post and we could abstract these out into a different variable as well so we could extract the params out into its own variable and then we could spread that onto this payload like this and this will make our schemas just a little bit tidier so now we're ready to create our handlers so come over to your controllers and create a post.controller.ts and we can split our window so we can look at our routes and see what handlers we need so we need a handler for creating a post so we'll export an async function and this async function again takes request and response and we can copy and paste this a few more times so we can just copy the names of the handlers from our routes and then if we come back to our routes and make sure we've imported all these handlers now we're ready to fill out each one of these handlers so to create a post we're going to get the user id from the request object we're going to call a create post service and then we're going to send the post back to the user so we need to import get from lodash and we need to import this create post service from our post services and we're also going to need to create the post service so come over to your services folder and our create post service is just going to take an input that is document definition with our post schema document our post document schema and then we're going to call post.create with our input so now we've imported our create post handler we're ready to go to our update post handler so our update post handler is going to call a find post service that we're going to need to create and a find an update service that we also need to create if we can't find the post then it's going to send back a 404 which means if it can't find the post then it's going to send back a 404 not found error so import find post and we'll import find and update and we'll implement those methods so find post is going to take two inputs it's going to take our query and that is a type filter query with our post document and it's going to take options and it's going to have a default set of options here with lean true so when you use this service you can specify lean false if you want to use the document if you want to use the mongoose functions on the document and so we pass this empty object in here because this is the because this is the projection object which we want to leave blank for now so i find an update method we'll take a query define the document an update query and some options so the next handler we need to implement is the get post handler and i get post handler is going to get the post id it's going to find the post and then it's going to send it back to the user we're just going to say in here if not post then send back a 404. we also have a small problem with our update post handler so if the user is trying to update a post that doesn't belong to them they're going to be able to do so if they're not logged in so we're going to do a quick check here to say if post.user is not equal to user id then we're going to send back a 401 and we're just going to pass the user id from post to a string to make sure that it's going to match the string type of user id and we need to get the user id from the request object and with that small check we've saved ourselves a lot of headaches the last handler we need to implement is the delete post handler so delete post is going to get the user id the post id from params it's going to try find the post again if it can't find it it's going to send back a 404 it's going to check that the requesting user belongs to this post or the post belongs to the requesting user it's going to call this delete post method and then it's going to send back a 200 request so we need to implement the delete post and so delete post just takes a query to find the document and then it will call delete1 and delete the document and that is our post cred operations complete let's go over to postman and make sure it all works so we're going to log back in and get a new access and refresh token you can use your refresh token to get a new token and you will get the new token in the request in the response headers but it's easier here just to log in again we're going to replace this access token and now we can create a post so you can see our new post here and we can replace our post id with our new posts id now we can get our post and you can see our post document is returned here and finally we can sorry we can update our post we can say this is a new title and we get our new title here and finally we can delete our post and our post is gone as always thank you for watching if you have learned something please like the video and subscribe to my channel and you can find the github repository in the description you
Info
Channel: TomDoesTech
Views: 23,882
Rating: 4.8936486 out of 5
Keywords: coding for beginners, coding, typescript, javascript, nodejs, tutorial, programming, restful, api, backend, server, http, https, rest api, express, ExpressJS, Express.js, node.js, node, MongoDB, Mongoose, Mongoose typescript
Id: goUbHgAzPCs
Channel Id: undefined
Length: 65min 57sec (3957 seconds)
Published: Mon May 03 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.