Supporting Promises in Express Middleware

May 21, 2019

Express doesn't support promises or async/await in middleware or routes. In the below example, the Express endpoint will never send a response because of an unhandled promise rejection.

const app = require('express')();

app.get(async function routeHandler(req, res) {
  // Will throw an error because `req.params.bar` is undefined
  req.params.bar.toString();

  // Request will hang forever because `res.json()` never gets called.
  res.json({ test: 42 });
});

const server = await app.listen(3000);

// Will time out. If not for the `timeout` option, would hang forever.
const err = await axios.get('http://localhost:3000', { timeout: 300 }).
  catch(err => err);
err.message; // "timeout of 300ms exceeded"

In order to make sure your Express app doesn't hang forever, you need to make sure your middleware functions call next() and your route handlers call res.send() or res.json(). The easiest way to do this is with the @awaitjs/express library.

const app = require('express')();
const { addAsync } = require('@awaitjs/express');
addAsync(app);

// @awaitjs/express adds a `getAsync()` function to Express
app.getAsync(async function routeHandler(req, res) {
  // The `getAsync()` function knows to look out for promise rejections
  req.params.bar.toString();

  res.json({ test: 42 });
});

const server = await app.listen(3000);

const err = await axios.get('http://localhost:3000').
  catch(err => err);
err.message; // "Request failed with status code 500"

If you don't want to use an outside library, you can handle errors yourself. With async/await, you can wrap your logic in a try/catch to make sure errors are caught. Just make sure your catch block doesn't throw an error.

const app = require('express')();

app.get(async function routeHandler(req, res) {
  // Wrap your route handler logic in a try/catch, and make sure
  // to respond if an unexpected error occurs.
  try {
    req.params.bar.toString();

    res.json({ test: 42 });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

const server = await app.listen(3000);

const err = await axios.get('http://localhost:3000').
  catch(err => err);
err.message; // "Request failed with status code 500"

If you're using promise chaining, you should use the Promise#catch() function.

const app = require('express')();

app.get('/', function routeHandler(req, res) {
  return Promise.resolve().
    then(() => req.params.bar.toString()).
    then(() => res.json({ test: 42 })).
    // Make sure you call `.catch()` on your promise to handle errors!
    catch(err => res.status(500).json({ message: err.message }));
});

const server = await app.listen(3000);

const err = await axios.get('http://localhost:3000').
  catch(err => err);
err.message; // "Request failed with status code 500"

Want to become your team's Express expert? There's no better way to really grok a framework than to write your own clone from scratch. In 15 concise pages, this tutorial walks you through how to write a simplified clone of Express called Espresso. Get your copy!

Espresso supports:
  • Route handlers, like `app.get()` and `app.post()`
  • Express-compatible middleware, like `app.use(require('cors')())`
  • Express 4.0 style subrouters
As a bonus, Espresso also supports async functions, unlike Express.

Get the tutorial and master Express today!

Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Express Tutorials