Search code examples
node.jsexpressmean-stackcode-organization

How to split a routes.js that grows too large to be easily maintained?


I'm using node and express to create a rest api. I followed a tutorial where all the routes and its logic are saved in a routes.js file like this:

SERVER JS:

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

(...)

require('./app/routes.js')(app, port, express); 

ROUTES.JS

module.exports = function(app, port, express) {


  var apiRoutes = express.Router(); 

(...)

//Sample route 

  apiRoutes.get('/userfiles', function(req, res) {
    UserFile.find({ owner: req.decoded.user.email }, function(err, filesList) {
      if (err)
        return done(err);
      res.json({ success: true, files: filesList });
    });
  });

My problem is twofold:

1 - Routes can easily contain code thats 150 lines long, some of them far longer. It doesn't feel clean to have route declarations and the logic grouped together. Is it a good practice to do something like this instead?

apiRoutes.post('/randomRoute', function(req, res) {
    return res.json(functionThatContainsTheActualCode(req));
  });

(and then have an functionThatContainsTheActualCode function with all the logic in a different file).

2 - I have middleware that applies to some functions (for example, some routes are only accessible for logged in users and those routes go through an authentication middleware). Currently way I do it is declaring public routes before the middleware declaration and private routes after, which feels incredibly hacky. How can I separate public and private routes (and the middleware itself) in different files?


Solution

  • Problem 1:

    We need to go deeper.

    Change the route file to just require the actual router logic.

    routes.js

    // where app = express();
    module.exports = (app) => {
        // index.js happens to be a file exporting the router.
        app.use('/', require('./index')); 
    
        // this is basically the idea. Create a separate file for the actual logic.
        app.use('/route', require('.path/to/file'));
    };
    

    and in file.js

    const express = require('express'),
        router    = express.Router();
    
    router.verb('/path/', (req, res, next) => {
       // do whatever
    });
    
    // this is required
    module.exports = router;
    

    Problem 2: Middleware is basically a function taking in request, response, next as 3 params, doing something with the request and either sending out a response or moving on to the next middleware. That's why you need to call next if you want to move to next middleware in the chain.

    Now all you need is a file that exports a function which takes request, response, next as params. // lets call this auth.js module.exports = function(req, res, next) {

        // do logic
    
        if () {
            return res.send(); // or res.somethingThatSendsOutAHttpResponse() 
        }
    
       // next middelware
        next();
    };
    

    Since express routes are also middlewares, (mind blown), you can mount them top down. To authenticate a route, just put the auth.js middleware on top of that route.

    router.get('/', require('./auth'));
    router.get('/', require('./log'));
    router.get('/', (req, res, next) => {
        // yolo
    });
    

    Now since this is web dev, you still got problems. Now all your boring database queries are scattered everywhere. Fear not, you can solve it, by, guess, creating another file.

    apiRoutes.get('/userfiles', function(req, res) {
        const userFile = require('/path/to/model/with/userfile/methods/exported/out');
    
        // do something with userFile's methods
      });