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.
1 | exports.postUpdateProfile = function(req, res, next) { |
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.
1 | var Q = require('q'); |
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 | app.get('/repos', auth.isAuthenticated, auth.isAuthorized, github.getRepos); |
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 | var S = require('spots'); |
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 | // isWithinRateLimit is another middleware function |
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
1 | var thinky = require('thinky')({ |
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.
1 | function getUser(connection) { |
This solves 1 problem, but introduces 2 new ones:
- Every call to get the User model needs the connection information, pushing the need to provide this information to every caller.
- 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.
1 | var R = require('ramda'); |
The server code executes at startup
1 | var connection = ... // for example using nconf |
Any other file can get the User model after that
1 | var User = require('../models.js')(); |
For more functional JS examples, see my other function javascript posts