For some time NodeJS has supported the async / await syntax, allowing you to avoid the many issues with Promises and Generators . However, out of the box, Express doesn’t handle async route controllers very well. Error handling in particular is problematic. However, with some simple function composition, it’s actually quite easy to fix this.

An example

Let’s say you have a route that needs to invoke and await an external asynchronous helper function called someAsync . It takes the result of that function and returns it via res.json as follows:

const someAsync = require('./helpers/someAsync') module.exports = async (req, res) => {

const result = await someAsync(req.body)

res.json(result)

}

That will work as an Express route controller just fine, however if something fails within someAsync you’ll never know about it because there’s nothing to catch the error. Sure you could add try / catch blocks within your route controller but that gets verbose and tiresome very fast.

A simple solution is to use function composition to wrap your async routes:

const asyncRoute = route => (req, res, next = console.error) =>

Promise.resolve(route(req, res)).catch(next)

You can then use this as follows:

const someAsync = require('./helpers/someAsync') const myRoute = async (req, res) => {

const result = await someAsync(req.body)

res.json(result)

} module.exports = asyncRoute(myRoute)

So now, if there’s an error, the wrapper will catch it and pass the error along to the next function. By default that function is just console.error but you can override this with your own custom error handlers.

Testing your async route

Using sinon and proxyquire you can test the route as follows:

const { expect } = require('chai')

const sinon = require('sinon')

const proxyquire = require('proxyquire') describe('src/routes/myRoute', () => {

const mockSomeAsync = sinon.stub() const myRoute = proxyquire('../../src/routes/myRoute', {

'./helpers/someAsync': mockSomeAsync

} const req = { body: 'some body' }

const res = { json: sinon.stub() }

const next = sinon.spy() const resetStubs = () => {

res.json.resetHistory()

next.resetHistory()

} context('no errors', () => {

const result = 'some result' before(async () => {

mockSomeAsync.resolves(result)

await myRoute(req, res, next)

}) after(resetStubs) it('called someAsync with the right data', () => {

expect(mockSomeAsync).to.have.been.calledWith(req.body)

}) it('called res.json with the right data', () => {

expect(res.json).to.have.been.calledWith(result)

}) it("didn't call next", () => {

expect(next).not.to.have.been.called

})

}) context('has errors', () => {

const error = 'some error' before(async () => {

mockSomeAsync.rejects(error)

await myRoute(req, res, next)

}) after(resetStubs) it('called someAsync with the right data', () => {

expect(mockSomeAsync).to.have.been.calledWith(req.body)

}) it("didn't call res.json", () => {

expect(res.json).not.to.have.been.called

}) it('called next with the error', () => {

expect(next).to.have.been.calledWith(error)

})

})

})

Code

I have bundled up this code into an npm repository. To use it in your code simply run:

npm i route-async

Then in your code:

const asyncRoute = require('route-async') const yourRouteHere = async (req, res) => {

// ...

}

module.exports = asyncRoute(yourRouteHere)

Links