Modular APIs with Express Router

Kenneth Ahlstrom
The Coder’s Guide to Javascript
3 min readMay 3, 2019

There are quite a few tutorials out there about how to get started with Node and Express as a backend for your frontend javascript framework of choice ( React, Angular, Vue, etc ). These tutorials usually get the user setup with a very basic API interface like the following:

index.jsconst express = require('express');
const bodyParser = require('body-parser');
const app = express();const port = process.env.PORT || 5000;app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/hello', (req, res) => {
res.send({
express: 'Hello World from ExpressJS',
});
});
app.post('/msg', (req, res) => {
console.log('You posted: ' + req.body.msg);
});
app.listen(port, () => console.log(`Listening on port ${port}`));

An example like the one above is quite adequate for getting started and building a simple API interface for a small app … but what about when things start to get more complex? For example, let us pretend that I am building a carpooling app and I need to query the API for User, Location, Vehicle, and Schedule information. If we keep everything within the root ExpressJS file, it’s going to start getting very large and hard to maintain before I’ve even finished defining all of the initial routes, much less any potentially complex logic associated with them.

The solution is actually rather straightforward. We will separate our concerns and build a modular API by utilizing the ExpressJS Router. Start by creating a new directory in your project called routes. Then, inside of that directory, we will create a new file, user.js, that will handle all of our user-based API routes. The only difference is that instead of using app.get() for our endpoint definitions, we will use router.get() (or post or put or delete). Doing so means that we also need to include express.Router() in our file. Here is our initial implementation of the user.js file:

./routes/user.jsconst express = require('express');
const router = express.Router();
// Get User by ID
router.get('/:id', (req, res) => {
console.log('UserID: ' + req.params.id + ' requested');
});
module.exports = router;

Note that our path definition for this endpoint is only /:id. We aren’t prefixing with a /user or /api/user or anything else. That will be taken care of in the next step.

Before this file will actually work as an endpoint, we also need to update our root Express file (usually called index.js or server.js or similar).

index.jsconst express = require('express');
const bodyParser = require('body-parser');
const user = require('./routes/user');const app = express();const port = process.env.PORT || 5000;app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/api/user', user);app.listen(port, () => console.log(`Listening on port ${port}`));

We have done two things in the file above. We included our user.js file from the routes directory and then we declared that we will use it when given the pathing of /api/user. Therefore, getting our User by ID requires the use of the relative path: /api/user/:id … the combination of the path we include with app.use in the file above and the path we included in user.js.

And that’s it. We can now duplicate the process for our Location, Vehicle, and Schedule objects in this carpooling app and all of our endpoints will be nicely separated and modularized. Many devs might even provide an index file within routes that imports all of the various different route modules and allows for our root file to require only the index …

./routes/index.jsconst user = require('./user');
const location = require('./location');
const vehicle = require('./vehicle');
const schedule = require('./schedule');
export default {
user,
location,
vehicle,
schedule,
}

… and then …

index.jsconst express = require('express');
const bodyParser = require('body-parser');
const routes = require('./routes/index');const app = express();const port = process.env.PORT || 5000;app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/api/user', routes.user);
app.use('/api/location', routes.location);
app.use('/api/vehicle', routes.vehicle);
app.use('/api/schedule', routes.schedule);
app.listen(port, () => console.log(`Listening on port ${port}`));

… but I have often found this practice to be less helpful in the long run than just explicitly providing individual routes within the root file. In any case, it’s a matter of personal preference and I thought I’d share the concept as it is often used.

Now … what do we do with all of our database queries that feed our API endpoints? Well, that’s a subject for another post … but it starts by creating a models directory and separating files in much the same way we have done with routes.

--

--

Kenneth Ahlstrom
The Coder’s Guide to Javascript

Developer, Snowboarder, Travel Enthusiast, Adventurer, ENTP. When I write, it’s because I have something to say.