How to store routes in separate files when using Hapi?

node.jsRoutesHapijs

node.js Problem Overview


All of the Hapi examples (and similar in Express) shows routes are defined in the starting file:

var Hapi = require('hapi');

var server = new Hapi.Server();
server.connection({ port: 8000 });

server.route({
  method: 'GET',
  path: '/',
  handler: function (request, reply) {
    reply('Hello, world!');
  }
});

server.route({
  method: 'GET',
  path: '/{name}',
  handler: function (request, reply) {
    reply('Hello, ' + encodeURIComponent(request.params.name) + '!');
  }
});

server.start(function () {
  console.log('Server running at:', server.info.uri);
});

However, it's not hard to image how large this file can grow when implementing production application with a ton of different routes. Therefore I would like to break down routes, group them and store in separate files, like UserRoutes.js, CartRoutes.js and then attach them in the main file (add to server object). How would you suggest to separate that and then add?

node.js Solutions


Solution 1 - node.js

You can create a separate file for user routes (config/routes/user.js):

module.exports = [
    { method: 'GET', path: '/users', handler: function () {} },
    { method: 'GET', path: '/users/{id}', handler: function () {} }
];

Similarly with cart. Then create an index file in config/routes (config/routes/index.js):

var cart = require('./cart');
var user = require('./user');

module.exports = [].concat(cart, user);

You can then load this index file in the main file and call server.route():

var routes = require('./config/routes');

...

server.route(routes);

Alternatively, for config/routes/index.js, instead of adding the route files (e.g. cart, user) manually, you can load them dynamically:

const fs = require('fs');

let routes = [];

fs.readdirSync(__dirname)
  .filter(file => file != 'index.js')
  .forEach(file => {
    routes = routes.concat(require(`./${file}`))
  });

module.exports = routes;

Solution 2 - node.js

You should try Glue plugin: https://github.com/hapijs/glue. It allows you to modularize your application. You can place your routes in separate subdirectories and then include them as Hapi.js plugins. You can also include other plugins (Inert, Vision, Good) with Glue as well as configure your application with a manifest object (or json file).

Quick exapmple:

server.js:

var Hapi = require('hapi');
var Glue = require('glue');

var manifest = {
    connections: [{
        port: 8080
    }],
    plugins: [
        { inert: [{}] },
        { vision: [{}] },
        { './index': null },
        {
            './api': [{
                routes: {
                    prefix: '/api/v1'
                }
            }]
        }
    ]
};


var options = {
    relativeTo: __dirname + '/modules'
};

Glue.compose(manifest, options, function (err, server) {
    server.start(function(err) {
        console.log('Server running at: %s://%s:%s', server.info.protocol, server.info.address, server.info.port);
    });
});

./modules/index/index.js:

exports.register = function(server, options, next) {
    server.route({
        method: 'GET',
        path: '/',
        handler: require('./home')
    });
});

exports.register.attributes = {
    pkg: require('./package.json')
};

./modules/index/package.json:

{
    "name": "IndexRoute",
    "version": "1.0.0"
}

./modules/index/home.js:

exports.register = function(req, reply) {
    reply.view('home', { title: 'Awesome' });
});

Have a look at this wonderful article by Dave Stevens for more details and examples.

Solution 3 - node.js

You can use require-hapiroutes to do some of the organization and loading for you. (I am the author so I am a little biased, I wrote it to make my life easier in managing routes)

I am a big fan of require-directory and and wanted a way to manage my routes just as easily. This lets you mix and match routes in your modules and modules in directories with routes.

You can then do something like this...

var routes = require('./routes');
server.route(routes.routes);

Then in your directory you could have a route file like...

module.exports = [
{
  method : 'GET',
  path : '/route1',
  handler : routeHandler1,
  config : {
    description: 'my route description',
    notes: 'Important stuff to know about this route',
    tags : ['app']
  }
},
{
  method : 'GET',
  path : '/route2',
  handler : routeHandler2,
  config : {
    description: 'my route description',
    notes: 'Important stuff to know about this route',
    tags : ['app']
  }
}];

Or, you can mix and match by assigning to a "routes" property on the module

module.exports.routes = [
{
  method : 'GET',
  path : '/route1',
  handler : routeHandler1,
  config : {
    description: 'my route description',
    notes: 'Important stuff to know about this route',
    tags : ['app']
  }
},
{
  method : 'GET',
  path : '/route2',
  handler : routeHandler2,
  config : {
    description: 'my route description',
    notes: 'Important stuff to know about this route',
    tags : ['app']
  }
}];

Always, good to have options. There is full documentation on the github or npmjs site for it.

Solution 4 - node.js

or you can use a index file to load all the routes in the directory

index.js

/**
 * Module dependencies.
 */
const fs = require('fs');
const path = require('path');
const basename  = path.basename(__filename);

const routes = fs.readdirSync(__dirname)
.filter((file) => {
    return (file.indexOf('.') !== 0) && (file !== basename);
})
.map((file) => {
    return require(path.join(__dirname, file));
});

module.exports = routes;

other files in the same directory like:

module.exports =  [
    {
        method: 'POST',
        path:  '/api/user',
        config: {
           
        }
    },
    {
        method: 'PUT',
        path:  'api/user/{userId}',
        config: {
           
        }
    }
];

and than in your root/index

const Routes = require('./src/routes');
/**
* Add all the routes
*/
for (var route in Routes) {
    server.route(Routes[route]);
}

Solution 5 - node.js

Interesting to see so many different solutions, here is another one.

Globbing to the rescue

For my latest project I settled on globbing for files with a particular name pattern and then requiring them into the server one by one.

Import routes after having created the server object

// Construct and setup the server object.
// ...

// Require routes.
Glob.sync('**/*route*.js', { cwd: __dirname }).forEach(function (ith) {
    const route = require('./' + ith);
    if (route.hasOwnProperty('method') && route.hasOwnProperty('path')) {
        console.log('Adding route:', route.method, route.path);
        server.route(route);
    }
});

// Start the server.
// ...

The glob pattern **/*route*.js will find all files within and below the specified current working directory with a name that contains the word route and ends with the suffix .js.

File structure

With the help of globbing we have a loose coupling between the server object and its routes. Just add new route files and they will be included the next time you restart your server.

I like to structure the route files according to their path and naming them with their HTTP-method, like so:

server.js
routes/
    users/
        get-route.js
        patch-route.js
        put-route.js
    articles/
        get-route.js
        patch-route.js
        put-route.js
Example route file routes/users/get-route.js
module.exports = {
    method: 'GET',
    path: '/users',
    config: {
        description: 'Fetch users',
        // ...
    },
    handler: function (request, reply) {
        // ...
    }
};

Final thoughts

Globbing and iterating over files is not a particularly fast process, hence a caching layer may be worth investigating in production builds depending on your circumstances.

Solution 6 - node.js

Try hapi-auto-route plugin! It's is very simple to use and allow prefix in your route path.

Full disclosure: I am the author of this plugin

Solution 7 - node.js

I know this is already approved. I put down my solution in case someone wants a quick fix and new to Hapi.

Also I included some NPM too so Newbees can see how to to use the server.register with multiple plugin in the case ( good + hapi-auto-route )

Installed some npm packages:

npm i -S hapi-auto-route

npm i -S good-console

npm i -S good


// server.js
'use strict';

const Hapi = require('hapi');
const Good = require('good');
const AutoRoute = require('hapi-auto-route');

const server = new Hapi.Server();

server.connection(
    {   
        routes: { cors: true }, 
        port: 3000, 
        host: 'localhost',
        labels: ['web']
    }
);

server.register([{
    register: Good,
    options: {
        reporters: {
            console: [{
                module: 'good-squeeze',
                name: 'Squeeze',
                args: [{
                    response: '*',
                    log: '*'
                }]
            }, {
                module: 'good-console'
            }, 'stdout']
        }
    }
}, {
    register: AutoRoute,
    options: {}
}], (err) => {

     if (err) {
        throw err; // something bad happened loading the plugin
    }

    server.start((err) => {

        if (err) {
            throw err;
        }
        server.log('info', 'Server running at: ' + server.info.uri);
    });
});

In your routes/user.js

module.exports = 
[   
     {  
        method: 'GET',
        path: '/',
        handler: (request, reply) => {
            reply('Hello, world!');
        } 
    },  
     {  
        method: 'GET',
        path: '/another',
        handler: (request, reply) => {
            reply('Hello, world again!');
        } 
    },
];

Now run: node server.js

Cheers

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCenturionView Question on Stackoverflow
Solution 1 - node.jsGergo ErdosiView Answer on Stackoverflow
Solution 2 - node.jscoquinView Answer on Stackoverflow
Solution 3 - node.jsBrian ONeilView Answer on Stackoverflow
Solution 4 - node.jsWhisherView Answer on Stackoverflow
Solution 5 - node.jsFredricView Answer on Stackoverflow
Solution 6 - node.jssitrakayView Answer on Stackoverflow
Solution 7 - node.jsMr HView Answer on Stackoverflow