Introduction to Express Middleware
In this video I'm gonna be talking all about Express middleware and teaching you everything you need to know. And at the end of this video I'm gonna go over all of the common problems people run into with Express middleware so you will make sure that you don't run into these same problems. Let's get started now.
Now with all of the middleware that you're gonna be writing after this video, you're gonna need somewhere to host your website and that's where today's video sponsor atlantic.net hosting comes in perfectly. They have an entire year-long free trial that you can take advantage of where you can try out their servers completely for free for an entire year. And if you use the code Kyle when you check out, you're gonna get an additional $50 of credit you can use towards hosting on top of the entire year of free hosting. So make sure you check out atlantic.net, link down in the description below, and use the code Kyle when you sign up.
Initial Express Server Setup
Welcome back to Web Dev Simplified. My name is Kyle and my job is to simplify the web for you so you can start building your dream project sooner. So if that sounds interesting make sure you subscribe to the channel for more videos just like this.
And for this video to get started, I have just a little bit of code written ahead of time to save you from watching the boilerplate. All I have is a simple Express server setup and I'm running it using nodemon so that we get auto refresh every time we make a change. For example, if I save and refresh I don't need to restart my entire server. And as you can see, I just have some really simple routes right now. We have a home page route and a user's route, which all they do is send out the text home page or the text users page if we go to that users page as you can see here. And we're gonna use this to explain everything you need to know about middleware.
But before I get started, if you enjoy reading instead of watching, I have an entire blog article that goes over middleware in-depth, which you can check out as well if you want to. I'll have that linked down in the description below.
What is Middleware?
So getting started with middleware, I want to talk about the most basic form of middleware and exactly what middleware is because you may not be familiar with the exact definition of middleware. Essentially all that middleware is is a function or program or something that is going to run between the time that the server gets the request and the time that the server sends the request out to the client. So when I click refresh here, I'm sending a request to the server and that gets to the server and this app.get processes it. And middleware is anything that happens between the time the server gets the request and the time that the server sends out a response.
So in definition, this function right here for our app.get('/users') is actually a middleware because it's happening in between the beginning of the response and the sending of the response. And that is something really important to understand with Express is that every single thing you write in relation to actions such as app.get for these different actions is going to be middleware. When you have your request and response variable that is a middleware that you are writing that is going to be acting in between the response sending and getting.
So this is the most basic form of middleware but really when you think of middleware, you don't think of the actions such as these request response actions here. You think of things that happen either before or after those actions.
Creating a Global Logger Middleware
So to get started, I want to write a very simple logging middleware. And the easiest way to get started with a middleware is just to create a function. So we're just going to create a function called logger. And in here what we're gonna do is just console.log('log') just like that. We're just going to be logging out some information, whatever it is that you want to log out. And this is going to take in a few different properties. We have our request, we have a response, and we have a third variable which is called next.
So request and response, those are self-explanatory. They are the request that's getting sent to you and the response that you're sending back. But next this is actually just a function and all you need to do with the next function is just call it. So for example, we can call next() like this. And what's going to happen is the next piece of middleware in line is going to be run when you call this next function. All of these different parameters up here also take in a next just like this and we can call next inside of here, but since this is the last piece of middleware, there's really no point in calling next because there is no other middleware to call after this. So that is why you almost never see this next inside of your middleware for your controller actions.
But here we have this logger, it's logging out just the text log and we're calling next properly. So in order to use this middleware we have a few different ways we can do it. The first way is we can say we want this to be global middleware. So if we say app.use() and we put in our logger here, we just pass it the name of the function we want. We don't actually call this function, we just pass it the name of the function because app.use() here it accepts a function that takes in these three different parameters. So it's expecting a function that takes request, response, and next. So we're passing it a function that takes those. And if we save here and I refresh my page, you'll notice this text 'log' gets printed out. And that's because this middleware is being ran and then we're calling next and then it's running our next piece of middleware which is here sending out our user's page.
We can even put a simple log in here, just as users page. And now if I refresh, you can see it logs and then it prints out users page, just like that. And we'll also put the same thing inside of our home page as well. So when we define a logger up here for our middleware inside of this app.use at the global level, this is going to be run before every single one of our other requests. So if we go to the home page, you can see we get our log being ran here as well. And if I save this and refresh, you'll notice we get our home page being printed after our log. So this is going to be global to our entire application.
The Critical Order of Middleware Execution
But something really, really important to note about middleware is it runs in order that you define it. So right now we have our logger being defined first and then our actions. If we tell our logger to run after our actions, you're going to notice something interesting. If we refresh our page, we only get home page being printed out and this logger is never being printed. And that's because inside of these actions we're never actually calling next. If we were to call next inside of here and now save and refresh, you'll see it calls home page and then prints out our logger because in order our app.get happens first and then our logger happens next. And since we're never calling next from our action up here, we're never actually getting into this logger down here.
This is why in general when you're creating global actions such as this logger, you always want to put these at the very top of your file so that they happen before all of your different controller actions.
Implementing Route-Specific Auth Middleware
So the next type of middleware that I want to talk about is going to be middleware that is specific to a single action. And you can define this type of middleware by just putting it inside of this app.get function. This function actually accepts essentially two things. It accepts your path here, which in our case is just the home path, and then it accepts a list of different middleware. Right now we're passing it one single middleware which is our normal request, but if we wanted to pass another middleware... we're just gonna copy this, paste this down, and we're just gonna call this auth. And we want to authenticate people that go to our users page here. It's gonna take a request, response, and a next. We're gonna log auth out of here. So what we do is we just pass this middleware to our function just like this. So we have now two middleware being passed: our auth middleware and then our actual action middleware here.
And if we save, refresh our page on the users version, you can see we have our log being printed because that's our very first middleware, then our auth is being printed next because it's the first in this list, and then lastly our users page is being printed because it is the next in line of our list of middleware.
Now something else that's really amazing about middleware is the ability for you to access the request and response parameters. So what if we wanted to check to see if our user is actually authenticated instead of just logging out our auth? Well, we can do that by just putting an if in here and saying if our request.query.admin... so if it has the admin query parameter set equal to the string of true. So that means that we have an admin user, so we can just call next and go on to the next portion of our function. Otherwise, if for some reason we don't, we can exit out early and just send something along the lines of 'no auth' and we'll send that down to our user. And now let's just delete these, save again, and when I refresh, I'm not passing anything in our query string that says admin is true, so we should fail this authentication check. And if we refresh, you see we get this 'no auth' being printed out just like this. But if I come up here and I just say admin=true, now you can see I'm getting through to the users page.
Passing Data Between Middleware
So this is a super important... inside of your middleware you're able to access your request and response in order to either exit out—so we never actually got to this function because we sent to the server and never called next—or you can just auth people like this by checking your query parameters and then calling next in this example. Same thing with our logger, we could print out something based on a request. We could print out for example the originalUrl. And now when we save and refresh over here, you can see our URL gets printed out down here inside of our logger. So that's another great use case for these different types of middleware.
But what if inside of our users controller action here we wanted access to whether or not the user was an admin or not? All we need to do is pass information to the function for our users, but we can't actually pass this inside of our next. We can't come in here and say true, that their user is an admin because if we save this, there's no way we can access this inside of here. This should always take request, response, and next. It's never going to take this true parameter that we pass in here. So in order to get around this, what you need to do is set variables on either your request or response because this function has access to the request and response variable. So if we say request.admin is equal to true, now we're actually setting this admin: true variable for our request. And up here inside of this function, we can just console.log and come in here and just say 'user is admin equals' and we just want to say request.admin.
Now if we save and refresh you can see user as admin equals true, but if we don't have that admin section in there and we save, you see that we're just getting this /users being printed out and then 'no auth' and this is never being called because again we're never calling next inside of the failure case of our authentication. So we're able to now pass variables from our middleware to other sections of our controller actions, whether it's other middleware or the final result of our action here.
Common Pitfall: Understanding the next() Function
Now that is the basics of what middleware does, but the biggest, biggest problem that I see people run into with middleware is the fact that they misinterpret exactly how this next() function call works. They assume that next() essentially works the same as returning from a function. So instead of having our function set up like this, let's say that our function instead was set up like this, where what we're doing is we're calling our if and if this is true we're saying request.admin = true and we call next(). Otherwise if this isn't true, we're just going to be calling this function down here, this res.send('no auth').
And if we save this and we come in here and we pass admin=true and we hit enter, you're gonna notice immediately a problem. We're getting an error down here and essentially it's saying 'cannot set headers after they are sent to the client'. What's happening is we're coming in here saying admin=true. Okay, then we call this next() function, up here we call all of this code, which is great. This finishes executing and then our function comes back down here, we finished executing next(), so we continue executing our code and call res.send('no auth'), which is why we're getting this error. We need to make sure that if we want to exit out of our function, we need to call return to stop this from executing any further because next() is going to come back and execute again as soon as the function that next() calls is done executing.
Now if we save this and refresh, you can see everything works as before with no more errors.
Advanced Pattern: Post-Request Logic with next()
And a great way to illustrate this a little bit more in depth is inside of our logger let's just come in here and log out the text of 'before' and we're gonna log out the text of 'after'. And this is going to be before our next() and then after our next(). And let's just for now remove this auth logging just like this so we just have users page and we have before and after. So if I refresh over here, you see I get the text 'before' being printed out. So it's coming into the logger, calling before, and then it calls next(). And this next() calls the next middleware, which in our case is this function here which logs out 'user' and then sends our user. And then after that's done, it comes back here and executes everything after our next, which in our case is 'after'. So it's going to in order 'before', and then 'users page', and then 'after'.
And this is super, super important to understand because it's really easy to write code and forget to include a return, and then you have problems where you don't actually want to call this code down here but you're accidentally calling it because you're never exiting out of this function after the next() finishes executing. This can be a really great trick though because if you want to run some form of middleware after every single one of your functions executes, it's really hard to do because we already know we can't just move our logger to the bottom because that's going to run into problems where we're not calling next inside of our controller actions. So what you can do instead is put your logger up at the top and then just call next() immediately and then after all of your controller actions and middleware finish, this is going to call after your next() is finished. So we're able to essentially use a middleware that's defined at the very top and have it execute after every single thing in our entire chain of middleware finishes executing, which is again really useful for certain logging situations where you want to log that a request finished executing so you can maybe log out what the response is going to the user.
Conclusion
The most important takeaway from this video though is that middleware is essentially just a function which takes a request, a response, and a next, and it will call next() whenever it wants to move on to the next form of middleware. And in between there it can modify the request and response however it sees fit. That is essentially all middleware is, but that doesn't mean that it's not powerful because you can do so much with these simple set of tools for middleware.
And with that said, I really hope you enjoyed this video and if you did, you should check out some of my other videos linked over here and subscribe to the channel for more videos just like this one. Thank you very much for watching and have a good day.