March 25th, 2024 × #middleware#nodejs#webdev
Middleware Explained
Explaining what middleware is and examples of how it's commonly used in web development for things like authentication, caching, error handling, etc.
Transcript
Scott Tolinski
Welcome to Syntax on this Monday, hasty treat. We're gonna be stepping into your context here. We're gonna be adding some information, and we're gonna be downloading Node directly into your brain. That's right. We're talking about middleware.
Wes Bos
little scatterbrained right now because gotta you know, when you get back from vacation, there's a 1,000,000 little things that you gotta gotta get to, but pretty pretty stoked and, and well rested. So ready to talk about middleware.
Middleware runs between request and response to handle logic
Scott Tolinski
Yeah. It's always good to come back from a vacation and talk about middleware.
Scott Tolinski
We talk about a lot of things on this podcast. And if you wanna hear CJ, Wes, and I talk about even more, head on over to our YouTube, which is, youtube.comforward/ at syntaxfm on YouTube. We're posting a ton of stuff. In fact, just the day we're recording this today, which will slightly date this, CJ released a video showing practical examples of has the actually, the the things that we talked about on this very show, and people said, we wanna see code. Well, CJ has given you code, and he built a little mini site where you can even play with these things now, and it's really, really pretty sweet. So check it out. Syntax FM on YouTube. If you have not subscribed, go over there and just break your trackpad.
Scott Tolinski
Just press that subscribe button so hard. So, let's get into it. Middleware, what is it? Why is it handy? Why do people care about it? Why do you use it? Yeah. I thought we'd talk about middleware. I think it's an interesting
Wes Bos
Yarn of building really any application, and this the concept of middleware applies to absolutely everything, systems design, but we're gonna be talking about it in terms of, like, you're building a back end web server and when might you need middleware Wes why is it handy? So middleware is for is a bunch of code or some code that will run in between the initial request and the actual event handler that handles your data that's coming in. So somebody visits a URL. They submit a form.
Wes Bos
They save something. They try to access a route that is behind a logged in state.
Middleware widely used for user authentication
Wes Bos
A lot of the times when you want to run some sort of logic or functionality before the user hits that route handler, you inject what's called middleware, which is a function that will run, and it could be multiple functions as well, and it's used to check if somebody has access to a specific route it's used to we'll we'll do a whole bunch of examples but generate some data from another system and bring it into that specific Wes, ability to skip expensive operations, take the logic out of a URL handler because sometimes you'll have a route handler that will do specifically something, and it doesn't make sense to muddy the logic of that route handler. Maybe, somebody saves an item to the database. Right? They update an item. They click the save button. It doesn't always make sense to put all of the logic behind authentication and, data parsing and, which which server is it going to, multitenant application. It doesn't make sense to put all of that logic into that specific route handler because you can just assume that those things are in place at that point and put that other logic into a middleware. So I've been using middleware for for quite a while. It's been a concept in Express. JS was was the 1st time that I've ran into it specifically via something called Connect, but all of the modern frameworks now as well have this concept of middleware. I'm gonna be talking about how to use it in in some examples as well. Yeah. And and to even just give a, food sandwich based analogy here, you could think of
Scott Tolinski
if the request is the sandwich going into your mouth, or the request response, you could think of the middleware. It's kind of being like the bread. Right? It's Yeah. Steps in between the meat of all the stuff you're doing at the top and the bottom of it. Right? You got you got the top layer. The moist the maker.
Scott Tolinski
You the Wes maker?
Wes Bos
Is that a my brother-in-law, is that I think that's a Friends reference, isn't it? I don't even know. Oh, no. It's a my brother-in-law is a big Friends person. I for the record, I hate Friends. I think it's a a garbage show.
Wes Bos
But the record, I I got Node I think it's a great show. There's 2 things that make that make me laugh about Friends. It's pivot and Oh, yeah. The moist maker. And the moist maker is the the idea that you have a, a sandwich, and you need to add something that gives you moisture. Right? Like, a a turkey sandwich left over for Thanksgiving, and there there's a layer that you add to that to add the moisture to it. And I've never seen that Friends episode, but I I get the reference.
Can skip expensive operations with middleware
Scott Tolinski
Okay. Well, either way, it it it steps into your request and response at the start and end of your process. It can do all sorts of stuff. So let's talk about real world examples. Like, what what might you use, middleware for? I think a big one that, you know, a lot of websites have is authentication. Right? And the way that authentication works in middleware is that that request comes in. Usually, that authentication token is in a cookie or something. Right? And what you do with that cookie is then you go look up the session that the user's logged in, or you look up the user, you check to make sure that they're properly authorized or authenticated.
Scott Tolinski
And then you load up that user data and, yeah, put it into context. And then that information for the user, the roles of whatever JS available for anything you need to do from that point forward.
Wes Bos
Yeah. And it's often makes sense to like, for example, in my application, I have forward slash admin, and then I have, I don't know, probably 15 different routes, different routes that can be rendered as well as different routes that can be update data. And I don't put the logic of, are they logged in and do they have the permissions in every single one of those routes. You put it in a single piece of middleware and you say, apply this middleware to any forward slash admin route handlers.
Wes Bos
And what that middleware would do is it yeah. Like Scott says, it looks up the current user. It if and it will look up their current, if they have access to those specific things. And if they don't, it throws an error, and it will render out a an error page say you don't have access to this specific thing. But if it does, it it puts the user on the request, and then any route that's after admin will now have access to the current user. And you can generally access it via something like request Scott user or request that data.
Wes Bos
Or how how you access those values is different in every single application, but generally, they just stick it into the request or make it, available via the async local storage API, which is new in in Node. Js.
Scott Tolinski
Yeah. Yeah. And and generally, like, when we're talking about this stuff, we're gonna be referring to that as context because that Mhmm. Makes sense.
Scott Tolinski
Wes just flashing his syntax sticker there on video. Can't we talk about that? Yeah. Wes put a syntax sticker on the bottom of the mug, so anytime he takes a drink, I can't remember. It's great.
Scott Tolinski
But so that when we when we refer to context in this episode, what we're talking about is less of, like, the technical, bones of some Implementation. Yeah. Implementation. Right? But we're more or less talking about the the concept of putting something into context, and I did little quotes there, that is available throughout the rest of your request cycle.
Wes Bos
Some other ideas for middleware that are are commonly used is redirecting users to a specific instance. So if somebody is coming into a URL that has been shared via American, this happens all the time in Node, sometimes you'll want to redirect that user to the Canadian version of that website. Or, if you have data privacy laws and you somebody is signing up for a specific use case, you could check what their IP address is or or where the request is originating from, and then you may want to save that user's data in a specific database that lives maybe in Europe or in a different region that you you must keep their data inside of that specific instance.
Scott Tolinski
Word.
Scott Tolinski
You could also use it for logging and stats. You know, sometimes, especially larger applications, they dump all of their logs into, maybe even a third party logging provider. Maybe they're writing it to files.
Scott Tolinski
But what you can do by logging in middleware is that request comes in. You have access to all of the information in the Wes, log it. You can even log how long a route took to resolve or how long, the process in between the start and end of this process is because, again, you have access typically in middleware to be the end in in the start and the end of the process.
Middleware useful for logging and analytics
Wes Bos
Maybe I should explain one more thing about middleware. The idea with middleware is that you get the request in. Right? You can add stuff to the the context if you want or you can log stuff, but the idea is that you you call, like, next or you return a new response from the middleware, and then it will continue on down your specific route. So what you could do in development mode is that if you only want to have logging turned on in development because you wanna you wanna have a nice verbose mode, you could just say if process dotenv equals development, then log that value out. Otherwise, don't specifically do that. That can also be really handy for trying to just temporarily turn on some debugging. Vercel something's going wrong in production, you could flip on some logging in a middleware. You're not actually changing the code that is running, and that's that's such a nice thing to just leave that as is and not have to modify it and simply just jump in the middle there.
Scott Tolinski
Yeah. And totally too. You can even, like, start a timer too. So that request comes in, you start a timer, you do all your stuff in the middle, and then you, log the end result of that timer. I was doing that in just outputting and then 3 different emojis based on how long that process took in milliseconds. If it was I forgot the actual numbers. But if it was slow, I just had a turtle output to my logs. If it was fast, I had a bunny. And if it was very fast, I had a rocket ship. And that's just in development.
Scott Tolinski
It was easy to see like, oh, if I'm working kind of casually, oh, this thing right here JS kinda always taking a really long time.
Scott Tolinski
Just just as a little canary in the coal mine, not necessarily any deep Scott of, understanding of the performance, but just a little canary.
Wes Bos
AB testing, really handy as well. If you are building a landing page and you want to alright. For 10% of the users or users who have this specific flag on or users that have a, actually, this is one thing I do is is country codes as well. I provide discounts for different countries based on where your user is coming, And what I do is I have a set middleware in there that when the request comes in, I check via a header that says what country are they from. And if they are from a specific country, I'll populate the full name of the country because, like, I'll just get, like, CA, and I wanna populate that to Canada. And then I'll also populate some information about coupon codes that they get. And then when it comes time to actually rendering the application, you can simply just check if that value is there. Say, if there is a coupon code, then render out the coupon code banner. Of course, you can you could change see which heading specifically works better, and you can get really, really, really complicated with the different AB testing values.
Handle error logging and reporting with middleware
Scott Tolinski
One last1. Well, I guess we can have even a few more.
Scott Tolinski
Otherwise, we're just gonna be spending the whole time here on examples, but, you could also have your error handling and logging. In fact, our Sentry set up, for making sure that our errors are captured and sent to Sanity. So that way, we can solve them in a very timely manner with Sentry's amazing tools and features.
Scott Tolinski
Just a little casual, I think.
Scott Tolinski
Wes. Well, that that's all being done in middleware as well. It just sort of steps in there.
Wes Bos
Other things, caching expensive renders. Obviously, we have things in on servers and in the browser and on CDNs to to do caching.
Wes Bos
But also you could use you can simply just memoize a function or implement your own caching in a middleware, which JS, say, alright. Well, if I've already done this query, I've got the data here in in a key value store. I could just return the data directly, and then it never even needs to hit that value. Because that's another thing that can happen from middleware JS you don't have to continue on in the middleware. You can simply just return early, and then the the request will never actually hit that later middleware.
Wes Bos
And the last 1 I have here is just multi tenant applications. So I run, I I don't know, probably 11 different domain names on a single, Node. Js application for all my courses.
Wes Bos
And the way that I determine which domain name and which course somebody is actually viewing is I run a set of middleware. So one of the very first middlewares that the request goes through is it says, if the domain name has, beginner JavaScript .com in it, then set the course code to Bos.
Wes Bos
And then later on in the rendering, it will choose which files to actually render out based on that data that had been set earlier. So if you that's that's an example of a multi tenant application. It's just me. I'm the only tenant. But if you were to have multiple customers being, running on the same code base, you can use a middleware to determine which customer is this when you go through the whole process, and you can even do that with databases. If you have multiple databases running for each of your customers, you might need to set the database connection string in a middleware before you hit any of those database calls.
Scott Tolinski
Yeah. And even another one that a lot of people have have used before whether they know it or not is that by default, when you have, like, form data submitted to your application or you're sending data to your server, that data isn't typically parsed, and Express did this with was it what what is the EXPRESS implementation of this called? The body parser? Body parser. Yes.
Scott Tolinski
Where that is essentially parsing data. In fact, I wrote one of these for SvelteKit to do that for me so that anytime I I submit a form, it's always available at locals.form, form data. So just being able to parse your data at any point in their request so that it actually comes in as a JavaScript object instead of, inside of the the headers or anything like that is a it's a nice little thing.
Wes Bos
Wes does it run? I think we covered that. It runs in the middle. Yeah.
Scott Tolinski
I think it runs on the edges.
Wes Bos
That's the way I think about it. Okay. Well, let's keep this in. Where does middleware run? Does it run-in the middle? Does it run on the edges? That's a good question. So, traditionally, they're with Express.
Wes Bos
It simply runs in the same application, and it just JS a function that runs before the rest of your your other functions run. Right? However, it's becoming more and more popular to run your middleware in a totally separate environment that's called an an edge function. It runs at the edge because if you're gonna stick a whole bunch of logic before your actual application runs, it better be fast as hell. Otherwise, you're gonna really extend the the load times of those specific handlers. So where a lot of these things now run is they run on the edge, and they run-in environments that are not typically full Node JS. So probably the most common one is running in a Cloudflare Worker.
Middleware evolving to run on edge for speed
Wes Bos
And the Cloudflare Worker will try to run it as close to the user as possible so you get the best response times, and it will run it in a paired down environment that doesn't necessarily have the whole Node.
Wes Bos
Js setup. Although, Cloudflare is pretty close to being Node. Js compatible as well right Node. And that's how the Vercel middleware and Next. Js middleware
Scott Tolinski
also runs in Cloudflare Workers. So you have the the same idea there. Yeah. And even when I think about the edge, I don't even necessarily think about the technical edge, but I think about, like, it runs at the edges of your application, right, before all the juicy stuff in the middle happens.
Scott Tolinski
Wes. You got yeah. You you you step in there and you say, hey. I'm doing some stuff. I'm I'm working here. And then you go into your actual stuff and then, you know, you come back and finish off your your middleware. So to me, like, I like to think about it like I mentioned before, it's like the bread of the sandwich. It's start and stop at the Sanity, and then all the the, the moist maker. I don't even know what you're talking about. The moist you got the moist maker as your regular route handler. All you got JS the your stuff at the middle is all your Yeah. Your your actual work. Right?
Wes Bos
You'll also hit time out limitations as well in a lot of these edge, areas, so it might not make sense to wait connect to a database. You might not be able to do a whole database connection setup. We talked about that if you go back to the episode we did on serverless databases.
Wes Bos
We talked about sort of limitations around all of that, but often people will forego the whole database connection string, or they'll use a database where where you can use it in a middleware, and they'll just stick stuff in, like, a key value store or something that's really, really fast to connect to and and access.
Scott Tolinski
Yeah. So yeah. We're we're talking a little bit about limitations here, but, you know, I I do you know, we briefly mentioned this. If you're doing too much work in your middleware, remember that's work that happens on every Wes, right? If every single time you're heading to your database to load up the user to do, that's a database call on every single request. So just be cognizant of what you're doing in this middleware.
Scott Tolinski
And if you need to do some things that are heavier, we've mentioned caching. Find a way to cache them or find a way to reduce that sort of that that load time. I you know, there that's a common thing is where people will put like a, some sort of, like, heavier data initialization or into their middleware without even thinking about it. And then sure enough, every single request comes in, you're having to do some process that you you might not have to do on every request.
Wes Bos
Yeah. For for the user one, I'm curious if you think that this I many, many years ago, I was like, is it okay to look up the user on every single request? And I came to the conclusion those yeah.
Wes Bos
You you could cache that for a little while if you really wanted to, but, it's totally fine, and it's very fast to do a quick database lookup of the currently logged in user based on their session, especially when you're need to update the user and and maybe permissions, things like that. It will be a pain in the butt if you have to cache data, and have to revalidate that.
Wes Bos
In every application I've ever done, I've just just query the current user on every single request, and it's never been issue for me. Have you done caching of that? Yeah. I've done caching of it, but with Redis. So,
Scott Tolinski
in in the way that we're doing that on Vercel up specifically JS, obviously anytime the user's updated that that cache is updated, but the cache is like per session. So when that auth token comes in, the first thing we do is check the cache to see if that session is is in there. If they log out or if that session is expired or whatever, we have all that information anyway so we can dump or or, you know, hit the database. But, like, typically, you're checking base user stuff. You're checking emails and roles and things like that. I I personally have that coming in from a a quick Redis check, and that's it.
Scott Tolinski
Which JS, you know, it can be very fast to do that way. Cashing and I did find not like a crazy amount of savings, but it's still savings nonetheless. You know, Node having especially if your database is living off-site, go another place, come back with that data. Maybe your user data has a ton of stuff on it.
Scott Tolinski
So you never know. Yeah. It it it is Yeah. That's that's a point. Successfully. Yeah.
Wes Bos
That's a good point. It's, like, what's in that query could significantly like, if you're querying the current user and all their courses and all their progress of every video and every transaction they have and you're sticking all of that, if if that's like, I don't know, like, 300 k of data, that has to go over the way or somewhere and then be stored in memory, and that certainly could could slow you down. So it is important. Also, like Scott said, you should probably throw some timers in there or use something to figure out where is the the time of this request being spent. You know? You could just say, oh, like, it's a 500 millisecond. That's kinda slow. But where is that 500 milliseconds being spent? And if that is in your user lookup, then it's probably worth throwing it into Redis. And the way that that would work is you you still keep your middleware for the user, but the early on in that user lookup middleware, you you check if it's in the cache and and the reddest cache, and that is fast as hell to be able to just quickly return the cache version rather than do a whole,
Scott Tolinski
round trip to the database. Yeah. Fast as hell for sure.
Wes Bos
Alright.
Wes Bos
Oh, last things that we have here. Next. Js middleware is 1 file only. So I love the express, and hono. Js does this as well where you you set up your your route. You say, alright, admin forward slash anything, and then you can have your populate user or check for auth or do they have access to this specific thing, and then that will then move on to the next row. I love doing that at a route Vercel. And the Next JS middleware is a 1 single file that will run on every single request, and you have to add the logic in yourself.
Wes Bos
There also is there's, like, matchers where you can say, alright. Only run this on this specific thing, but you're essentially reimplementing the entire router yourself.
Wes Bos
And I was like, why is there Scott, like, a middleware file that you could stick in the app router? Like, I wanna I wanna go to the admin folder and put a middleware Oh, yeah. That'd be file in there Oh, wow. And then have that run only on it. And, apparently, they had tried that, and it was very confusing, and I could see how it could get kind of muddy. I I was like, a very simple example. I was talking about it maybe sounds like a good idea, but, obviously, they had tried it. So that was kind of a bit of a bummer to me because I was like, oh, like, you you have this app router. Right? Like, I don't have to write a router. It's just folders.
Wes Bos
But but then if you want middleware, you do have to write your own router and you have to match the the URLs and you say if it starts with this, and you gotta make sure that that's not, like, injectable, you Node, like you had to make sure that, the user can't accidentally come up with a URL that matches your regexes. So I I thought that was a bit of a a pain in the butt to to have to do that. But I've I've been I Scott the fix. For that in in SvelteKit world. Layouts.
Scott Tolinski
In in SvelteKit, you have the concept of having, like, a server side layout. And so let's say you're in the admin section, you could toss essentially what you would toss there into the the like, let's say Wes have forward slash admin. We put layout Scott server and forward slash admin. You do your checks. You do your routing there. That's going to run before it does literally anything else in that that route section. So, technically, if you have, like, a server side layout type of deal like that, you could you could throw it in there, and it would be the same.
Wes Bos
That's at least how I accomplished that type of thing. Yeah. I that's what I wanted to do. I was like, I want this to work like the syntax website. Yeah. Right. Yeah. As far as I could tell, it it doesn't doesn't work like that, especially if you want it to run at the edge in in the middle rather than, be part of the the actual generation.
Scott Tolinski
Yeah. Word.
Wes Bos
And then also connect, connect style. We've talked about express, fastify, pretty much any Deno JS, Any framework you pick up will have this concept of middleware, or hopefully, we'll have this concept in the middleware, and,
Scott Tolinski
you just gotta gotta take a look at it. What does it look like in your specific application? That's it. That's it. Anything else to add there? I I don't. Yeah. Middleware, you can sometimes find them on Npm. Sometimes you just write them Vercel, and, you know, these types of things, I think you get more comfortable with it and eventually just become something you do on every project.
Scott Tolinski
But just about every single application I rate has middleware in it. Yeah. And it does all kinds of stuff for me. So, yeah. If you're not using middleware, it's it's probably a good idea to look into it. And only that, I think it will clean up some of the stuff that you're doing in individual routes so that you don't have to do it in the individual route itself.
Wes Bos
I I just remembered Node more thing I forgot to say is that, like, often these middleware you just said you can npm install them. The reason you can often NPM install them is because they are standard space. So they are either connect style, meaning that they have a request, a response, and the next function, and connect style will work with Fastify Express, the all kinds of different frameworks, or there'll be the new modern Vercel, which is the fetch or web request web response Wes you're simply just returning a fetch request or returning a response object, and those will work, or they'll be something that is somewhat a variant on that.
Wes Bos
And that's why you can just usually Npm install them all. And, like, you could go and NPM install, like, a rate limit. That's another really good use case as well as you you wanna stop somebody from hitting your sign up endpoint a 1000000 bazillion times, you could write a middleware. I have this in in my own application where it stops people from, hitting it too many times. It's a rate limit middleware, and you simply just need to Npm install it. And they work with more than just 1 specific framework.
Scott Tolinski
Yeah. We had something called the ban hammer on Vercel editorials, where if people tried like, I think it was, oh, man. There were some there there were if they if they had had a failed credit card attempt for, like, 3 times or something in a row, I don't know. There were some number. We had attached something to the user object, and it we would permanently ban them. And the way that the banning worked JS it applied a class that made it look like the user was logged out, but it wouldn't let them log in because they were already logged in. It was like a nice little clever, like, they can't they'll just be, like, what am I I I can't log in anymore. Oh, man. Or every single refresh,
Wes Bos
make the opacity
Scott Tolinski
one percent.
Scott Tolinski
Paul, just a vessel. Yeah.
Scott Tolinski
Love it. Scammer.
Wes Bos
Oh, that's good. Or every 1 in every 10 Wes, show them, but then Scott. I don't know. There's a lot of fun stuff you could do there, but that is middleware. Hopefully, you enjoyed that. We'll catch you later. Peace.