Check out a free preview of the full REST & GraphQL API Design in Node.js, v2 (using Express & MongoDB) course:
The "Routing with Express" Lesson is part of the full, REST & GraphQL API Design in Node.js, v2 (using Express & MongoDB) course featured in this preview video. Here's what you'd learn in this lesson:

Express Routing module has many features including pattern matching, multi-router support and support for all HTTP verbs.

Get Unlimited Access Now

Transcript from the "Routing with Express" Lesson

[00:00:00]
>> Scott Moss: So we talked a little bit about routing. We just did some catch alls, some verbs, some paths. We're gonna do like some sub routing and all that fancy stuff next. So routing, like I said with Express you have flexible pattern matching, handles parameters. And it just handles, in your route definitions, you can handle parameters, but in the controllers you can also, you have access to the query string as well.

[00:00:24] You just don't define query strings in the actual route definitions themselves. Query strings aren't explicit, so back to routing. Express has multirouter support. So I talked about, yeah, we had that app, right? And we declared routes on it with verbs, but remember I said you can also do sub routes and different paths that you mount on, which is very flexible.

[00:00:45] It allows you to like, here's the API behind this and then here's like authentication over here and then here is like admin stuff over here. So you can do that is really cool. And then they don't really need to know about each other at all, they are completely isolated.

[00:01:00] But they can also share global littleware and global configuration from the app router. We also have static and dynamic configuration. So static configuration is what we just did, right? So we declare some routes here. This is statically, you can also declare stuff dynamically at run time. It's really advanced, we will not get to that, but you can do it.

[00:01:24] Support for all HTTP verbs. This only really applies to rest. When we get to GraphQL nobody cares about verbs anymore right? So yeah, you got get, put, options, delete, post. Whatever the verb you got it. Express has got it. And it's order based so what that means is if I were to set up another route here, check this out.

[00:01:48] If I were to say app.get this one, and I were to say,
>> Scott Moss: If I were to do this and I were to save it. And then let's just start this up again.
>> [SOUND]
>> Scott Moss: Because I did that one first [SOUND]
>> Scott Moss: It should respond first.
>> Scott Moss: Right first true, that's because I declared that route before I declared this catch all.

[00:02:27] Order does matter in Express, so keep that in mind. This get for the index was declared before this all on every route, therefore when I did activate this path, it's response was handled. But I can show you, and we will get to this like you can pass the control of this to the next one.

[00:02:49] So you can say, cool, I want this to run first, but I don't want it to be the one that decides what goes back. I'm gonna pass it on to the next thing that matches. And that's when we get into middleware, so that's where it gets really crazy.

[00:03:01] But we'll get there. So yeah, order based,
>> Scott Moss: Cool. So the thing about routing in Express is that you can pretty much do whatever you want. At the end of the day, there's literally nothing you can't do. And that could be good or bad, because you can develop all types of patterns that could benefit you, but it also doesn't really give you a constraint.

[00:03:26] You might have patterns that are just very wild. You might have issues with routes being resolved, you might have issues about which middle where is attaching to which route. Because you can do everything its very good practice to try to develop some type of standards for your application, before you start declaring your routes.

[00:03:45] Something that I like to do, and that's what we're gonna be doing here, is I like to mount different routers on different paths that have different purposes. So let's say I'm building an app that has an admin side, it has a customer side where they have an API, then it has an analytics side, what I would do is for every single one of those high level features, it would be behind a different router.

[00:04:08] And then they have their own configuration associated with them. Because if I'm exposing API for my customers to use, it's probably a different set of authentication mechanisms that I'm gonna use there then I would for my admin APR or for my analytics APR. So I'm going to mount them behind different paths.

[00:04:26] So for instance, for /API, use this router, for /Analytics, use this router, for /whatever, use this router. And that router has its own routes inside of it, and that's what we're gonna see. So I'm just gonna do a little bit of demonstration on that and then we're gonna jump right into the next example.

[00:04:45] So basically [COUGH] what you can do. I'm just gonna do it right here. So we have this app right? And let's say I want to make another router. I can come in here and I can say I want to make a apiRouter. All right. And with that I can just come in here and I can say, express.Router.

[00:05:06] Like this, with a capital R and evoke it. And notice that I use express.Router not app. So just that right there kind of tells you that, this app router does not care about this app right here. We're not even using the same thing. So they're completely two different things.

[00:05:23] What this apiRouter, is now, I have a separate router, I can attach routes to it just like I did with this app. So now that I have this apiRouter I can say apiRouter. I want you to respond to a get request on index. We'll get here in a minute.

[00:05:39] And then what I wanna do is same thing, I'm just gonna say res.json, and I just gonna go like api: true, right? So we got that. Now, remember I said routes are order based, right? Can put order base, I just made new router, I register a Handler for the index path on the get verb for that router.

[00:06:03] And I did it before this one. So if were to make a get request to this path, am I gonna see api: true or am I gonna see first: true?
>> Group: first: true.
>> Scott Moss: Okay, wow. And why is that?
>> Speaker 3: Their router isn't listening. It's not connected is it?

[00:06:22]
>> Scott Moss: It's not connected exactly yeah. I never, it just I didn't even connect it to the app. It's just here. I just declared it but I need to like associate it with this app because remember by default they have no association. I just declared a router that isn't connected with app.

[00:06:36] Everything has to be connected to the main app here. The thing that's listening which is this app. So yeah, cool story, you declared a route, but you didn't tell your app about it. So that's the next thing we would have to do, so to do that is, it's an apiRouter, so hey let's mount it on the API path.

[00:06:52] So what I'll do down here, I'll say app.use, and you can think of use as like, it's almost the exact same thing as .all, to be honest. Use is more like when you do use your pretty much are saying I'm going to use middleware here and not so much a final response.

[00:07:13] app.all is like I'm gonna respond here but they can pretty much be used synonymous. There's a small difference, but the difference is not that big. So we're gonna say app.use. And we're gonna say for this path /api we want to use the apiRouter. So now when I go to, when I make a request besides api, it's gonna activate the apiRouter and its routes which is index.

[00:07:40] So this index is referencing the index that is mounted on. This is not the index of our app, this is the index of the router. And the router is mounted on the API. So therefore, this route is /api. So for instance, if I put a /thing here, this full route is /api/thing.

[00:07:58] Cuz it's mounted at API, so we can actually test this out. So let's do that.
>> Scott Moss: And then I'll come here. I'm already listening. I'm just gonna restart that, just in case.
>> Scott Moss: And then now if I go and I run this command again with no API, you see I still get first true.

[00:08:17] I didn't hit the API, but now when I say /api I get API true. Right, cuz I hit the API. And then if I go /api/whatever, I'm not gonna get anything because, I'm sorry, I'm gonna get okay false because it hit right here, right. This catch all is gonna prevent any 404.

[00:08:40] No matter what I do, this catch all's gonna respond, so right now my API We'll never have a 404 because there's always a catch all at the bottom. Yes?
>> Speaker 4: Constap equals express, would that also work if you did express.router?
>> Scott Moss: Would what also work?
>> Speaker 4: Would the app run if it was constap=express.router?

[00:09:04]
>> Scott Moss: No, because you need to mount it. Well you need to mount a router to something that's eventually listening. So if you made app a router, well, you can't listen on a router, so you wouldn't do anything. It would execute and it would exit immediately, right? Cuz the reason our server's running right now, the thing about node, node what it does is it's asynchronous, right?

[00:09:26] So it will run your code, and then anything that's async gets put in a task, a queue. And it's gonna come back as an executed. When that queue is emptied Node just exits. There's nothing, there's no more work to be done so it stops. The reason why our code is still running right now on our server is because we are listening on a port.

[00:09:45] There's always something to be done. There's always something listening. That event queue is always there. So that's why it's there. If we did what he's suggesting there would be nothing listening. Therefore, the code would execute just fine. And then it would just exit immediately. Your script would be done before you blink.

[00:09:58] Nothing would happen. But it'll execute the code, just nothing will happen. So yeah, eventually a router has to be attached to some root app that is listening to some port somewhere. And that's how you get this to happen.
>> Speaker 5: Is it best practice to break out your apiRouter into a separate file and then import it?

[00:10:17]
>> Scott Moss: Yes.
>> Speaker 5: Because you said it's still going to be order-based. So you can set up the order of this particular router or route, the API Manage that separately, import it here.
>> Scott Moss: Yep.
>> Speaker 5: Okay.
>> Scott Moss: That's exactly what we're gonna do. So I'm glad you're seeing that pattern, cuz yep, you can pretty much see it, yeah.

[00:10:34] Cuz you could imagine this apiRouter's gonna have tons of routes, tons of configurations. And inside of it, it might have its own subrouters. For api/todos, here's the to-do router, right? And then there's so much configuration. You you totally wanna Make sure you don't put all that in one file.

[00:10:51] One, cuz you're not gonna be able to test it, two, you're gonna have merge conflicts everywhere, [LAUGH]. So yeah, totally don't do that. And remember that order, the whole thing about order matters applies everywhere, right? So if I had this apiRouter, and I make another route on the apiRouter, for a post on the same row, then, I'm sorry hold on.

[00:11:18] Lets do just another get for. We'll see that one and then what we'll do we'll say star here. There we go.
>> Scott Moss: There we go. So because I register the index of the api route first before I register the catch all, if I were to go to /api/anything else, we should see it respond with apiAll versus what we did before which was okay true.

[00:11:53] All right so let's try that one. So if I go to api, api true. If I go to api/ anything else I should see apiAll. All right, everybody follow me there? Let's go back. I set up a catchall. So, let's diagram what's happening here, on this route. So, we come through.

[00:12:16] We make a GET request to /. Right, so we make a GET request to /. What's gonna happen is express is going to look It's gonna come in here. It's gonna be like, okay, you're at /, but then we're actually at /api. So it's like okay, cool, you're at /api, let me execute this router.

[00:12:35] So now we're inside of this router. So now express is up here. And because the route that we hit was just /api, and it was a GET request, it actually stopped right here. It was like, cool this satisfies that, /api, root, which is the route, get verb, yeah cool, I'll execute that.

[00:12:54] But then the next time we did GET, we set /api/whatever, it did the same thing. It came down as like, okay, cool, api's here. Activate that router Nope, that's not satisfy that. Okay, here we go we got the .all so any verb, okay cool, any paths or anything that is not this is any verb I'm going to go ahead and run that, that's what happen.

[00:13:19] Before when I get rid of this, if I want to get rid of this the reason to execute this one down here. Is because it still comes in here, that api, goes apiRouter. It's like, I don't have anything for apiRouter that happens here, so I'm gonna go to the next route that satisfies this request and that's this one, even though it's upper level.

[00:13:44] It's still serial, it's still in order, but it's nested, right? This is all still in order. This apiRouter is in front of this app. And it has its own nested routes, but it eventually comes out, it's a tree. It comes out of that tree to the parent and it goes back down, right?

[00:13:59] It's a depth-first, it does depth-first routing, right? Not breadth-first. So it's gonna go all the way down to the root, and be like, I don't know what I'm doing here. Let me go back up, check some more, go back up, check some more. So it's depth first. Anybody's who's ever done binary searches or stuff like that, it's a depth first versus a breath first.

[00:14:19] The difference is breath first is like, let me check everything on this level first. Before I go down one level, is like, I'm gonna go all the way down until I can go down anymore. To the leaf. Then I'm gonna come back up and do it again, come back up and do it again.

[00:14:32] And it's going to do that until it figures out which one it finds. So, that's what's happening here.