Hapi: Best Practice Project Structure

Created on 23 Mar 2015  路  17Comments  路  Source: hapijs/hapi

When building an ReST API project, what would be the best project structure to keep things HAPI?

I currently have the following structure

  -node_modules
  -server
     -server.js
      -routes.js
      -config
          -config.js
          -db.js
      -controllers
          -example.js
      -models
          -example.js
      -server.js
  -package.json

Most helpful comment

One of the best applications that i used and its quite handy and configurable.

-node_modules(0)
-private (0)
--modules (1)
---exampleModule (2)
---exampleAPI.js (2)
---exampleRouteController.js (2)
---exampleRoutes.js (2)

With this approach everything is clear and the only thing that you need to do is to load the modules that way:

  • Server.js
const gitRoutes = require('./private/modules/exampleModule/exampleRoutes.js');

for (var route in gitRoutes){
    server.route(gitRoutes[route]);
}
  • exampleRoutes.js
module.exports = function() {

/**
 * @param {GET} METHOD
 * @param {req} exampleParam
 * @param {req} exampleParam
 * @param {req} exampleParam
 * @param {reply} exampleParam
 */

    return [
        {
            method: `GET`,
            path: `/api/user/{id}`,
            config: {
                handler: exampleRouteController.getUser,
                description: `exampleDescription`,
                notes: `exampleNotes`,
                tags: [`api`, `exampleTag`],
                validate: {
                    payload: {
                           name: Joi.string().min(1).required(),
                           id: Joi.number().min(100).max(999999999)
                    }
                 }
            }
        },
    ];
}();

  • exampleRouteController.js
        get: function getUser(request, reply) {
            var promise = request.models.User.find({
                where: {
                    email: request.params.email
                }
            });

            var source = Rx.Observable.fromPromise(promise);
            var subscription = source.subscribe(
                (next)=> {
                    return reply({
                        payload: {
                            response: next,
                            status: 'OK'
                        }
                    });
                },
                (e)=> {
                    log('onError: %s', e);
                    subscription.dispose();
                    return reply({
                        payload: {
                            err: e,
                            status: 'ERROR'
                        }
                    });
                });
        },
  • exampleAPI.js
function ExampleAPI() {
    console.log('Loaded Module: ExampleAPI, Path: /private/modules/User/exampleAPI.js');
}

ExampleAPI.prototype.spawnChildProcessSequence = function(obj, timeout){
};

ExampleAPI.prototype.childProcess = function(dir, cmd, arg, timeout){
};

---exampleAPI.js - Main module logic
---exampleRouteController.js - Handler only for routes and create your custom reply structure(errors, events etc.)
---exampleRoutes.js - All your routes configuration

  • In this structure you have multiple modules which you can combine if you need.For example another api that uses the same feature just copy and paste it where you need :).
  • You can spawn child processes for every single module(if you wish to)
    for example if over time you found out that most of your users are using 3 modules and your api power is overwhelmed with only these 3 modules in this case you can spawn 3 independent api which serve only for these modules and you can pass via socket for availability of the current api and switch the client to preferred api if you wish to for load balancing.Most of the times you just spawn new docker container and you don't give a shit about that :D.
    Anyway there are some people which prefer to create a self sustained system and others choose just to use available resources and enjoy its your choise. :))

Please share your opinion with this approach i will be really happy :)))

Have a hapi..... happy coding :D

All 17 comments

@puchesjr +1 I'm interested in what others have to say. I personally take advantage of hapi's ability to separate logic into modules. Here is the structure try to follow

    node_modules
    lib
        middleware
            db.js
        routes
            user
                user-routes.js
                user-controllers.js
        models
        server.js
        index.js
    package.json
    index.js

I treat everything I can as a plugin.

I try to use the plugin system as well and make every entity a plugin e.g. user plugin, contact plugin and then my handlers/controllers orchestrates what needs to happen

I have built a core base to handle schema loading, database connections, logging, configuration and loading core routes. I can then load different logic into the core via plugins which encapsulate specific logic. My current structure looks like this

    node_modules
    lib
       logger.js
       schema.js
       config.js
       dataStore.js
       methods.js
       routes.js
       core.js
       plugins.js
   routes
       endpoints
           admin.js
           api.js
           dataStore.js
       handlers
           admin.js
           api.js
           dataStore.js
   plugins
       plugin1
       plugin2
    schemata
       schema1
       schema2
       schema3

the problem with that is if you get a lot of endpoints the endpoints and handlers folder will quickly be unhandleable

The project is quite large and I haven't encountered that problem

@simon-p-r what do you use to distinguish 'endpoints' from 'handlers'? Are the endpoints the main configurations and the handlers the functions handling each of the routes?

Yes the endpoints are the config object and the handler is the request and reply function? I use a module to require directories of exposed endpoints and concat the arrays to load into hapi. It works well so far.

Cool. That would jive with the way I like to order the addition of my routes. I like to have one big table where you can see all the exposed endpoints within an application or plugin with clear code-paths to where the functionality supporting the Endpoints resides.

Yeah it gives some clarity over what you have loaded, I also expose an endpoint that is a viewer into the route table which also helps when developing.

@simon-p-r i get annoyed quicker than you I guess :D

Still fresh to it all at the moment, time could change that though!?

let me know ;)

It's worth checking out a project called stimpy-medium, which built upon work done in hapi-ninja. Like many hapi projects, it's a plugin that can be optionally run standalone as a server. It splits configuration, controllers, and route definitions up across several folders, organized by creating files of the same name in those folders. For example, controllers/api.js would hold controllers for any routes defined in routes/api.js.

project/
- app.js
- node_modules/
- bussines/
-- reports.js
- dam/
-- reports.js
- dtm/
-- report.js
- utils/
- public/
-- assets/
--- jQuery/
--- css/
--- font/
--- images/
--- etc/
- routes/
-- index.js
- utils/
-- dateTimeUtil.js
- view/
-- js/
-- css/
-- index.html

One of the best applications that i used and its quite handy and configurable.

-node_modules(0)
-private (0)
--modules (1)
---exampleModule (2)
---exampleAPI.js (2)
---exampleRouteController.js (2)
---exampleRoutes.js (2)

With this approach everything is clear and the only thing that you need to do is to load the modules that way:

  • Server.js
const gitRoutes = require('./private/modules/exampleModule/exampleRoutes.js');

for (var route in gitRoutes){
    server.route(gitRoutes[route]);
}
  • exampleRoutes.js
module.exports = function() {

/**
 * @param {GET} METHOD
 * @param {req} exampleParam
 * @param {req} exampleParam
 * @param {req} exampleParam
 * @param {reply} exampleParam
 */

    return [
        {
            method: `GET`,
            path: `/api/user/{id}`,
            config: {
                handler: exampleRouteController.getUser,
                description: `exampleDescription`,
                notes: `exampleNotes`,
                tags: [`api`, `exampleTag`],
                validate: {
                    payload: {
                           name: Joi.string().min(1).required(),
                           id: Joi.number().min(100).max(999999999)
                    }
                 }
            }
        },
    ];
}();

  • exampleRouteController.js
        get: function getUser(request, reply) {
            var promise = request.models.User.find({
                where: {
                    email: request.params.email
                }
            });

            var source = Rx.Observable.fromPromise(promise);
            var subscription = source.subscribe(
                (next)=> {
                    return reply({
                        payload: {
                            response: next,
                            status: 'OK'
                        }
                    });
                },
                (e)=> {
                    log('onError: %s', e);
                    subscription.dispose();
                    return reply({
                        payload: {
                            err: e,
                            status: 'ERROR'
                        }
                    });
                });
        },
  • exampleAPI.js
function ExampleAPI() {
    console.log('Loaded Module: ExampleAPI, Path: /private/modules/User/exampleAPI.js');
}

ExampleAPI.prototype.spawnChildProcessSequence = function(obj, timeout){
};

ExampleAPI.prototype.childProcess = function(dir, cmd, arg, timeout){
};

---exampleAPI.js - Main module logic
---exampleRouteController.js - Handler only for routes and create your custom reply structure(errors, events etc.)
---exampleRoutes.js - All your routes configuration

  • In this structure you have multiple modules which you can combine if you need.For example another api that uses the same feature just copy and paste it where you need :).
  • You can spawn child processes for every single module(if you wish to)
    for example if over time you found out that most of your users are using 3 modules and your api power is overwhelmed with only these 3 modules in this case you can spawn 3 independent api which serve only for these modules and you can pass via socket for availability of the current api and switch the client to preferred api if you wish to for load balancing.Most of the times you just spawn new docker container and you don't give a shit about that :D.
    Anyway there are some people which prefer to create a self sustained system and others choose just to use available resources and enjoy its your choise. :))

Please share your opinion with this approach i will be really happy :)))

Have a hapi..... happy coding :D

I have a lot of custom sql in my handlers that I would like to abstract out as it is becoming hard to manage. What would the right way to handle that? Also I saw a lot of people referencing controllers, what do they do?

@sfabriece Post your questions to hapijs/discuss.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shamsher31 picture shamsher31  路  5Comments

leore picture leore  路  4Comments

hueniverse picture hueniverse  路  4Comments

midknight41 picture midknight41  路  4Comments

leore picture leore  路  3Comments