How to organize ExpressJS routes

Node.js became one of most import technologies when we are speaking about web development.  When we are speaking about Node.js we are thinking about non-blocking, real-time applications, high performance, etc.

When you decide to implement an application using Node.js you have to decide which framework you should use based on your project requirements and then it’s when Expressjs comes (in many cases). Although there are others great Node.js frameworks such as Hapijs.

Expressjs reached version 4.x and with this new version some changes were introduced.  Especially on middleware and routes, if you want to know more about changes between version 3.x and 4.x you should visit Expressjs page or this great article. Today, we’re going to explore how we should organize our routes using Expressjs 4.x assuming three different scenarios.

It was introduced a new implementation called “Router” which allows us to create instances to define our routes easily. Furthermore there is a new method app.route() which is an instance of Router “class” that could be very useful for small projects.

How to use app.route()

var express = require( "express" );
var app = express();

// We need a route to allow get and create a user.
app.route( '/user' )
	.get( function( req, res ) {  })
	.post( function( req, res) {  });

How to use Router

var express = require( "express" );
var app = express();

// get a new instance of Router
var router = express.Router();

// We need a route to allow get and create a user.
router.route( '/user' )
    .get( function( req, res ) {  })
    .post( function( req, res) {  });

// OR
router
    .get( '/user', function( req, res ) {  })
    .post( '/user', function( req, res) {  });

// "tell" application that we are using this route 
app.use( '/', router );

Both examples above do the same although the second one is more flexible because you can easily change the base path without changing all route paths.

Now we’ve already understood how app.route and Router work we’ll focus on what we need to think when we are structuring a project. Assuming we are structuring our project under MVC pattern route files should be separated from controllers files. In my opinion, it makes the project more flexible, easier to (re)organize and maintain. Routes project represents an important part of your project and you need to implement them thinking about what type of project it is. I mean how big, is it going to grow, future features, etc.

In this case we’ll use three different scenarios to expose how we should organize our routes based on our project complexity or size:

Small project

In this case we have a very small project  with some pages. I’m thinking about a simple web page or a simple RESTfull service. In this context you can just create a file routes.js where you’ll define your routes.

//# routes.js

module.exports = function( app ) {
    var controller = require( 'path-to-your-controller' );
    
    app.route( 'home' )
        .get( controller.homePage );
    
    app.route( 'login' )
        .get( controller.loginPage );
        .post( controller.doLogin );

};

OR

//# routes.js

module.exports = function( app ) {
    var express = require( "express" )
      , router = express.Router()
      , controller = require( 'path-to-your-controller' );

    router.route( 'home' )
    .get( controller.homePage );
    
    router.route( 'login' )
    .get( controller.loginPage );
    .post( controller.doLogin );
   
    app.use( '/', router );
};
//# app.js

var express = require( "express" );
var app = express();
//  requires and execute routes module.
require( 'routes' )( app );
.
.
.

 

Medium project

When you have a medium project to implement maybe you need to think more about how you’ll organize your routes as well as your project. Usually when we are working with medium projects we try to organize application in modules where each module represents each part of our application. Based on this thought a good way to organize your routes should be split your routes in several files where each files represents a module/application part.
For this example we can imagine our application has an user, a group and an invoice modules then each route file should represent an application module.

//# routers/user-route.js

module.exports = function( ) {
    var express = require( "express" )
      , router = express.Router()
      , controller = require( 'path-to-your-controllers/user-controller' );
    
    router.route( '/' )
        .get( controller.list )
        .post( controller.create );

   router.route( '/:id' )
       .get( controller.get )
       .put( controller.update )
       .delete( controller.delete );  
   
   return router;
};

 

//# routes/group-route.js

module.exports = function( ) {
    var express = require( "express" )
      , router = express.Router()
      , controller = require( 'path-to-your-controllers/group-controller' );
    
    // SIMILAR TO user-route FILE

    return router;
};

 

//# routes/invoice-route.js

module.exports = function( ) {
    var express = require( "express" )
      , router = express.Router()
      , controller = require( 'path-to-your-controllers/invoice-controller' );
    
    // SIMILAR TO user-route FILE

    return router;
};

 

//# app.js

var express = require( "express" );
var app = express();

app.use( 'user', require( 'routes/user-route' )() );
app.use( 'group', require( 'routes/group-route' )() );
app.use( 'invoice', require( 'routes/invoice-route' )() );
.
.
.

 

Big project (loading routes dynamically)

Now imagine a scenario similar to the previous one but much bigger with other needs such as public component and private component. A project with thousands of routes where several people will be coding. In this type of project we have to find a better solution to make it easier to maintain, e.g avoid every time you have to create a route file have to add a require on the app file. To avoid this kind of issues we can implement a dynamic way to load our routes files.

Routes project structure could be something similar to the following representation:

routes
    private
        user-route.js
        .
        .
        .
    private-route.js
    public-route.js
app.js

The main idea behind this structure is load public-route.js and private-route.js on app.js and then private-route.js dynamically loads all route files inside private folder.

//# app.js

var express = require( "express" );
var app = express();

// Add main route files.
require(  'routes/public-route' )( app );
require(  'routes/private-route' )( app );

app.listen( 80 );

Module public-route.js is responsible for defining all public routes. I mean all routes which don’t require authenticated users.

//# routes/public-route.js

module.exports = function( app ) {
    var express = require( "express" )
      , router = express.Router()
      , controller = require( 'your-path-to-controllers/public-controller' );
 
    // Possible public routes.
    router.get( '/home', controller.homePage );
    router.get( '/login', controller.loginPage );
    router.get( '/contacts', controller.contactsPage );
    router.get( '/clients', controller.clientsPage );
    
    // Add router to middleware
    app.use( '/', router );
}

Module private-route.js is the key of this structure because it have to check if users are authenticated and loads all private files routes dynamically.

//# routes/private-route.js

module.exports = function( app ) {
    var express = require( 'express' );
      , router = express.Router();
      , fs = require( 'fs' );
      , routes = [];

    router.all( 'app/*', function( req, res, next ) {

        // Should check if user has valid authentication.

        next();
    });

    // Read dir to get all files into it.
    // In this case you can read sync because this 
    // script just runs once ( when nodejs starts ).
    routes = fs.readdirSync( './routes/private' );
	
    // Travel all private routes files.
    routes.forEach( function( routeFile ) {
        // Require and execute route module.
        require( './private/' + routeFile )( app );
    });

    app.use( '/', router );
};

Mobule user-route.js is an example of what can be a route (private) which is loaded by private-route module.

//# routes/private/user-route.js

module.exports = function( app ) {
    var express = require( 'express' )
      , router = express.Router()
      , controller = require( 'your-path-to-controllers/user-controller' )(); 

    router
    .route( '/' )
        .get( controller.list )
        .post( controller.create );

    router
    .route( '/:id' )
        .get( controller.get )
        .put( controller.update )
        .delete( controller.delete );

    app.use( '/app/user', router );
};

If you are interested in exploring/running a simple project with this structure please visit this github project.

Conclusion

As you can see handling routes using ExpressJS it’s very easy and with its 4.x version this task becomes easier. There isn’t a prefect rule to structure your routes  you have to understand how they work and try to structure them based on your project requirements. Following this document you get great information about how ExpressJS routes work and how you should organize them. There are many other aspects to explore. I recommend  you to consult official documentation.

  • Terril Jamon Douglas

    Nice work on this article just what i needed.

    • mquintal

      Thank you Terril.

  • Renzo Canepa

    Thanks for this article. Is always good to have some real case scenarios
    about how should a project be structured. What do you think about
    splitting the routes directory in subdirectories (one for every model).
    Should be the private-route.js file updated to find the routes recursively?

    • mquintal

      Thank you Renzo! If you are thinking about some big application which have complex modules could be a nice way to organize your routes. And yes in that case you should change private-route.js file to read recursively otherwise you won’t have a dynamic structure.