Organizing your app routes with the Express 4 Router

Organizing your application structure and its routes is one of the first problems you will encounter while developing with Express. To help you with that version 4 added the Router class.

Let’s use it to define some routes in a cars.js file.

var express = require('express')
  , router = express.Router()

// Car brands page

router.get('/brands', function(req, res) {
  res.send('Audi, BMW, Mercedes')
})

// Car models page

router.get('/models', function(req, res) {
  res.send('Audi Q7, BMW X5, Mercedes GL')
})

module.exports = router

You define the brands and the models routes and then you export the entire router, so that you can use it elsewhere. It makes defining controllers in an MVC style app much more modular. Moreover, it will later help you when you build larger more complex applications.

Let’s load your new routes in an Express app.

var express = require('express')
  , app = express()

app.use('/cars', require('./cars'))

app.listen(3000, function() {
  console.log('Listening on port 3000...')
})

This app will respond to /cars/brands or /cars/models requests, with the data you defined above.

The most important bit, from the app file above, is

app.use('/cars', require('./cars'))

It loads the router with its routes and defines a prefix for all the routes loaded inside. The prefix part is optional. You could write for example

app.use(require('./cars'))

Then the server will respond only to requests from /brands and /models.

Using the routers for structuring your app

Let’s use those features in a bigger MVC app with the following file structure:

controllers/
  animals.js
  cars.js
  index.js
models/
public/
tests/
app.js
package.json

cars.js will be the same as above and animals.js will define a few more routes.

var express = require('express')
  , router = express.Router()

// Domestic animals page

router.get('/domestic', function(req, res) {
  res.send('Cow, Horse, Sheep')
})

// Wild animals page

router.get('/wild', function(req, res) {
  res.send('Wolf, Fox, Eagle')
})

module.exports = router

Again, you define the routes and then the router is exported, nothing new.

Your next file controllers/index.js will be responsible for loading all controllers, which you’ve already implemented. It will also define some more routes but without prefix, like a home page and an about page.

var express = require('express')
  , router = express.Router()

router.use('/animals', require('./animals'))
router.use('/cars', require('./cars'))

router.get('/', function(req, res) {
  res.send('Home page')
})

router.get('/about', function(req, res) {
  res.send('Learn about us')
})

module.exports = router

Every router can load other routers. This is very handy when you are organizing your app. You can even build a hierarchy of routers and routes, if you really need it.

I prefer to keep it only one level deep as in the example above, but your needs may vary.

In the file above, you first include your existing routers with their routes and then you define two more routes, before as usual to return the current router which contains all routes.

To load them, you only need to load the controllers/index.js file. Moreover, it is an index file, so you don’t need to provide its name when requiring it, you only need the folder name.

var express = require('express')
  , app = express()

app.use(require('./controllers'))

app.listen(3000, function() {
  console.log('Listening on port 3000...')
})

This is all you need to begin serving all routes defined in all your controllers. Your app will be responding to requests to the following paths.

  • /
  • /about
  • /animals/domestic
  • /animals/wild
  • /cars/brands
  • /cars/models

Notice how easy is to organize your routes with the Router class. All dependencies are only in one direction, from the base controller to leaf routes and routers.

Routers don’t know where they are used, they only know the routers they themselves include. This greatly simplifies dependencies and also makes the code more maintainable.

Router specific middlewares

Middlewares in express are extremely useful. They can load sessions, extract useful data, put common headers and much more.

While most of the time you add middlewares for the entire app with app.use, sometimes it is also useful to have them only for some routes. In such cases you would usually add the middleware to every individual path definition.

var express = require('express')
  , router = express.Router()

// Middleware

function authorize(req, res, next) {
  if (req.user === 'farmer') {
   next()
  } else {
    res.status(403).send('Forbidden')
  }
}

// Domestic animals page

router.get('/domestic', authorize, function(req, res) {
  res.send('Cow, Horse, Sheep')
})

// Wild animals page

router.get('/wild', authorize, function(req, res) {
  res.send('Wolf, Fox, Eagle')
})

module.exports = router

In larger more complex apps, having to add the middleware to each individual route quickly becomes tedious. In such cases you can add the middleware directly to the router.

var express = require('express')
  , router = express.Router()

// Applying middleware to all routes in the router

router.use(function (req, res, next) {
  if (req.user === 'farmer') {
    next()
  } else {
    res.status(403).send('Forbidden')
  }
})

// Domestic animals page

router.get('/domestic', function(req, res) {
  res.send('Cow, Horse, Sheep')
})

// Wild animals page

router.get('/wild', function(req, res) {
  res.send('Wolf, Fox, Eagle')
})

module.exports = router

The middlware will be executed only for all routes defined in this router and only for them. This can be used for defining whole controller authorization or making some input data transformation which will be then used everywhere inside the router or another common task.

Other benefits

The benefits of using routers will be felt on the all parts of your project. Separating the controllers with their own routers makes them more modular and easier to test.

You can also move around your controller both inside your file structure and in your path hierarchy, without affecting your code. All of this makes maintaining your application and working with larger team easier.

What’s next

If you are starting a new project you can clone the base-express repository from GitHub and use it as your starting point. It is already using the patterns shown above and it has a few more handy stuff. It is also always up to date with the latest version of Express and the latest versions of a few other libraries.

If you already have an existing app and you would like to use the Router class, I strongly recommend to first update to the latest express. Then rewrite the parts of your code that need rewriting. It will be easier and faster than you think. It will also result in much cleaner code and structure which is also easier to test and maintain.


Other articles that you may like

Did you like this article?

Please share it

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2024 Terlici Ltd · Terms · Privacy