Modularize Your Chat App

Nick Giancola
Philosophie is Thinking
5 min readMar 13, 2015

--

(or How to Write a Node.js Express App in More Than One File)

So you want to build an app with Node.js (io.js if you’re feeling adventurous) that does something other than manage your todo list or let you chat with randoms through websocket connections. Maybe you have some experience with robust web frameworks like Rails or Django. You have a taste for style and find 2000 lines of JavaScript in one file to be unacceptable.

You’ve selected Express as your minimalistic framework because you have some doubts about the maturity of bigger frameworks like Sails. Now what? Every example you find is a single file: app.js. The following tips aim to help ease the pain of structuring a Node.js app in a modular way, while testing it thoroughly along the way.

Single entry point

It’s important to note up-front that even though we are splitting our objects into many files and directories, our Express app will still have a single entry point. It is in here that our Express app will be initialized, middleware configured, and various other global setup will happen.

Skip ahead to “But how do I run it?” if at any point you’re unsure of how a particular tip can be applied in reality.

Organizing your file structure

Split up that single file app.js by breaking out objects into individual files and structuring the filesystem in a conventional way so future developers can easily follow. You might do something like:

models/
controllers/
lib/
config/
... etc

Routing

Splitting your routes into individual controller files means your routes are now distributed and thus need to be aggregated and initialized with Express in order for those paths to be exposed to the user. Enter express-enrouten, an Express middleware built by the kraken team at PayPal that will automagically load your controllers/ directory and handle your routing with some nice conveniences for namespacing: get(‘/api/users/:id’) becomes get(‘:id’) because of its location in controllers/api/users/index.js.

Requiring your components (no more ‘../../../../../’)

Node’s require allows you to a) require files relative to the directory of your current file; and b) require files relative to the node_modules/ directory in your package. The implication of (a) in a nested file structure is that you must traverse the tree up in order to require files from sibling directories:

// controllers/api/users/index.js
var User = require('../../../models/user');

Now that’s no fun and gets out of hand with deep nested directories.

You can leverage Node globals to expose a new function to all of your files that will behave as an app-specific version of require, scoped to your app’s root directory.

app_require.js

var path = require('path');module.exports = function() {
global.appRequire = function(name) {
return require(path.join(__dirname, name));
};
}();

Now our controller looks like:

// controllers/api/users/index.js
var User = appRequire('models/user');

Much better.

Note: there are a number of other solutions to this, but I found the above to be the most convenient and expressive.

Connection and configuration dependencies

If your app is like most apps, it will require configuration and shared connections to one or many databases. Node’s require cache allows us to share singleton connections or configuration objects thanks to its caching the value of module.exports. For example, if we have two models that need the database connection:

// models/user.js
var DB = appRequire('config/db');
DB.model('User', ...);
// models/article.js
var DB = appRequire('config/db');
DB.model('Article', ...);

And — voilà! — both models are defined on the same DB singleton, where config/db.js looks like:

module.exports = function() {
var db = new DatabaseConnection();
// do something to initialize your database settings
return db;
}();

The same pattern can be followed with configuration objects, whether they be JavaScript objects, simple JSON files, or ENV vars backed by a tool like nconf.

But how do I run it?

Following through on the “Single entry point” note above, in order to run our app we need to provide some glue that brings all the pieces together. We’ll split this into two pieces for some modularity that will help later:

app.js

// Require app_require first so it’s added to `globals` and available everywhere
require('./app_require');
var express = require('express'),
enrouten = require('express-enrouten');
// Initialize app
var app = module.exports = express();
// Add middleware
app.use(enrouten());
```

server.js

var app = require('./app'),
http = require('http');
var server = http.createServer(app);
server.listen(process.env.PORT || 3000);
server.on('listening', function() {
console.log('Server listening on http://localhost:%d', this.address().port);
});

$ node server.js — and you (should) have a running app.

By having app.js available as an instance of the Express app that is independent of the http port binding, we can easily require the app without running an http server. This could be useful for many things, but we’ll focus on how we use it in tests.

And how do I test it?

First you’ll want to find a good test runner; mocha will do the job (and you can integrate it nicely with grunt too). Add your tests in test/ and configure mocha to run tests from that directory. (I prefer to mirror the directory structure of the app, with each test file ending in _test.js. This postfix is not only expressive but also allows for configuring the test runner to only run _test.js files so you can control when other supporting files in test/ are executed — like test/boot.js, utils, etc.)

Bootstrapping your app

To run integration tests against your actual Express app (for instance, to integration test an API endpoint), you’ll need a running instance of the app available within your test suite. You may also want to run other global initialization (like app_require from earlier) to make your test suite behave similarly to your actual app.

To do this we’ll create a test/boot.js and configure mocha to run that before our suite. If you’re using grunt-mocha-cli, this would be done with the src option: src: [‘test/boot.js’, ‘test/**/*_test.js’]. You can do this with $ mocha as well.

test/boot.js

require('../app_require');before(function(done) {
global.testApp = appRequire('index');
testApp.on('start, function() {
done();
});
});

Now both appRequire and testApp will be available globally within your test suite, so you can write integration tests with supertest like so:

describe('GET /api/users', function() {
it('does something cool', function(done) {
request(testApp).get('/api/users').expect(200, done);
});
);

You can use boot.js to configure other functionality for your entire suite in before/after or beforeEach/afterEach hooks — like database cleaning and various forms of setup/teardown.

Testing niceties

One last trick: you can leverage Node globals to make frequently used testing utilities available to all of your tests without the need for repetitive requires. For instance, in boot.js I’ll set globals for chai’s expect and supertest’s request, because I’ll need them in many (if not all) of my tests. Say what you will about abusing globals, it certainly makes for a nicer testing experience.

global.expect = require('chai').expect;
global.request = require('supertest');

In conclusion

Express is an elegantly simple tool for building web apps — Node is too, for that matter. It provides a great foundation to grow from, and with the right kind of nurturing you just might reach a state of coding pleasure and responsible codebase maintainability. But getting from 0–60 your first time around can be a bit daunting, so I hope these tips help you speed up along the way.

--

--