Express, the Sinatra of Node.js web frameworks, is a piece of software I’m very fond of for its lack of enforced structure. You can structure your Express.js application any way you like. But like the dude said to Spider Man,
“With great power comes great responsibility”
I’ve worked through quite a few iterations of different Express apps from real tiny ones to very large scale ones and I thought that maybe it would help someone one day if I shared how I organize my Express routes from the file structure to the actual code.
Let’s start off with my project structure. You’ll likely need to configure your development environment and you should also have a build tool set up to help with repetitive development tasks and build/deploy tasks. With that in mind, this is how I structure these applications:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
That’s not everything of course but the important thing to look at is the
server/routes folder. In it we have a file called
index.js. This file is responsible for
require()ing all of our other route files. It allows us to break up routes into separate modules. Before I talk about how to organize routes properly I’ll show you some ways which don’t work.
How not to write routes
Method 1: Everything on the
In simple Express tutorials you’ll see an entire Express application set up like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
This works great when you have just a couple of routes but it’ll fall apart and get messy fast. Using
app.route() is an improvement but the problem is that you still have all of your routes defined in your main server startup script. You’ve got to break it out of there.
Method 2: The easy module way
The next thing you could try is by making all of your routes modules and break them into separate files. This actually works really well but you’ll soon notice that there’s something off about this. It just doesn’t feel right and when something doesn’t feel right in your code there’s definitely a better way to do this. I structured a very large scale Node application in this way and we did keep things organized but the routes themselves were full of messy code and it was confusing to understand how some objects made their way into the routes. Here’s what that looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
So before we go on, let’s go over what this is doing. You’ll see that when we load up our routes we’re using it as a function and passing it our app, database, and config objects. This is because
routes/index.js will be exporting a function that takes these arguments and uses them to further require more files and set up our routes. Here’s what
routes/index.js looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
There’s a lot of code there but it basically does this:
- Loop through all files in the
- If the files found are JS files and not named
index.jsthen require them and run them as functions passing in the app, database, and config objects for the routes to use
- If what’s found is a folder then recurse into it and run steps 1 and 2
There’s a third part to this, the actual routes themselves. Above we’re seeing a utility that’s requiring routes but here’s what the actual routes themselves look like:
1 2 3 4 5 6 7 8
The route is pretty simple but it requires you to pass around these sometimes heavy objects from the server start script all the way to the route itself. This works but there’s a better way. Instead why don’t we use Express’
Router() object and set up our routes as a series of middleware.
Method 3: The best way I know of
Rather than putting all of our routing logic directly on the
app object and passing around all sorts of heavy objects on each request, we can instead set up our routes as a series of middleware that rely on Express’
1 2 3 4 5 6 7 8 9 10 11 12 13
So far it looks much like what we had before and yes, we are passing the
app object to the route index file. But that’s the extent of passing around heavy objects. It doesn’t go any further than that. Here’s the route index file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Here we have a list of blacklisted file names. The code here runs through all of the files in the
routes/ directory and then mounts each one as an Express middleware at the URL prefix that matches the file’s base name. So for example, if a route file is named
users.js then we’ll mount the file’s routes at
/users. It ends up being translated as
Now we can start treating our routes as true controllers and have separate middleware for them if we need it as well. Here’s what the actual route file looks like:
1 2 3 4 5 6 7 8 9 10
This route would be mounted at
/users and then you can work with each route/controller as if it were its own application (but remember that global middleware will still be applied to it).
And there it is. The best way to structure Express routes. Another bonus to this is that it makes it trivially easy to mount separate versioned APIs in the future if you wanted. I may write more about doing this in the future. Hopefully that all made sense. Enjoy your more organized routes.