Less boilerplate in express app

Simplify middleware using higher-order functions.

Fewer nested callbacks

Instead of using callbacks in your middleware, use promises. This will greatly simplify the code, especially if there are multiple asynchronous steps to execute before moving to the next step.

original code to update user profile information
1
2
3
4
5
6
7
8
9
10
11
12
13
exports.postUpdateProfile = function(req, res, next) {
// using Mongoose / MongoDB
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
user.email = req.body.email || '';
user.profile.name = req.body.name || '';
user.save(function success(err) {
if (err) return next(err);
req.flash('success', { msg: 'Profile information updated.' });
res.redirect('/account');
});
});
};

The code above uses a typical callback-taking DB layer (in this case ORM Mongoose). In both async operations we had to handle a possible error by executing if (err) return next(err);. Let us simplify this code using promises. We can even keep using Mongoose or switch to promise-returning DB like PouchDB. Let us call User.findById using Q wrapper.

chain of promises to update user profile information
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var Q = require('q');
exports.postUpdateProfile = function(req, res, next) {
Q.ninvoke(User, 'findById', req.user.id)
.then(function (user) {
user.email = req.body.email || '';
user.profile.name = req.body.name || '';
return Q.ninvoke(user, 'save'); // A
})
.then(function success() {
req.flash('success', { msg: 'Profile information updated.' });
res.redirect('/account');
});
.catch(next); // B
};

Notice an interesting fact. We can avoid a nested call to the function success by returning a promise in the first step (// A). We also handle all errors in a single spot. No matter where the errors happen (even inside the success function), they will always be caught by the single .catch handler (// B). We also avoid extra code using the point-free style call .catch(next);.

Remove the same auth middleware arguments

Often when creating API routes we include the login / auth middleware callbacks. For example, here is typical code defining routes that should check first if the user logged in, and if the user has permission to access this specific route.

1
2
3
app.get('/repos', auth.isAuthenticated, auth.isAuthorized, github.getRepos);
app.get('/repos/:user/:name', auth.isAuthenticated, auth.isAuthorized, github.getRepo);
app.get('/repos/view/:user/:name', auth.isAuthenticated, auth.isAuthorized, github.viewFile);

Each app.get method call receives the same middle 2 arguments. Only the first arugment (path) and the final argument (controller) are different. Can we remove this duplication? Usually I remove the duplicate arguments at the beginning of the call using partial application, but in this case I need to leave the first argument position open. Luckily there are libraries like lodash or even a single utility function spots providing the selection application feature.

1
2
3
4
5
var S = require('spots');
var authGet = S(app.get, S, auth.isAuthenticated, auth.isAuthorized).bind(app);
authGet('/repos', github.getRepos);
authGet('/repos/:user/:name', github.getRepo);
authGet('/repos/view/:user/:name', github.viewFile);

The created authGet function is bound to the app object and leaves the spot for the first argument (path) unoccupied. Plus all extra arguments after the two bound callbacks are up for grabs. We can even pass more additional callbacks if needed. For example we can easily add request throttling to fetching a particular file even if the request is authorized

1
2
3
4
5
// isWithinRateLimit is another middleware function
authGet('/repos/view/:user/:name', isWithinRateLimit, github.viewFile);
// equivalent to
app.get('/repos/view/:user/:name', auth.isAuthenticated, auth.isAuthorized,
isWithinRateLimit, github.viewFile);

Initialize DB models once

Several ORMs require passing an instance of the database or connection to create models. For example thinky built on top of RethinkDB uses this approach. One creates thinky object that connects to the DB immediately. Any module that needs User model has to pass the DB connection information

models.js
1
2
3
4
5
6
7
8
9
10
var thinky = require('thinky')({
host: 'localhost',
port: 27015,
db: 'myDb'
});
var User = thinky.createModel('User', {
id: type.string(),
name: type.string()
});
module.exports = User;

Our model / business logic layer should NOT know how to connect to the DB just to create the User model. Thus I switched the logic around and pass this information into the models file. This means I need to move the initialization into a function to be exported.

models.js
1
2
3
4
5
6
7
8
9
function getUser(connection) {
var thinky = require('thinky')(connection);
var User = thinky.createModel('User', {
id: type.string(),
name: type.string()
});
return User;
}
module.exports = getUser;

This solves 1 problem, but introduces 2 new ones:

  1. Every call to get the User model needs the connection information, pushing the need to provide this information to every caller.
  2. We will open a new connection in each call to get the user.

I solve both problems by wrapping the exported getUser function with Ramda.once function and call it at the server's startup with the real connection information. Every other user of the User model can just call the method without any arguments to get the cached result.

models.js
1
2
3
4
5
6
7
8
9
10
var R = require('ramda');
function getUser(connection) {
var thinky = require('thinky')(connection);
var User = thinky.createModel('User', {
id: type.string(),
name: type.string()
});
return User;
}
module.exports = R.once(getUser);

The server code executes at startup

server.js
1
2
var connection = ... // for example using nconf
require('./models')(connection); // User model is cached

Any other file can get the User model after that

inside some controller
1
2
var User = require('../models.js')();
// User is the same cached value

For more functional JS examples, see my other function javascript posts