Express is the most popular HTTP server framework for Node.js, but unfortunately it doesn't have good support for async/await. Express doesn't handle errors in async functions. Fastify is an alternative Node.js server framework that enjoys much better support for async/await in addition to better performance. In this article, I'll show you how Fastify works with async/await and show you the corner cases you need to be aware of.

Hello, World with Async/Await and Fastify

Like Express, the "Hello, World" case works well with async/await. Below is an example Fastify server that waits for about 100ms before responding with "Hello, World!".

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

app.get('*', async function(request, reply) {
  await new Promise(resolve => setTimeout(resolve, 100));
  reply.send('Hello, World!');
});

await app.listen(3000);

Where Fastify beats Express is handling errors in async route handlers. If you throw an error in an async route handler in Express, Express will never send a response. Fastify actually handles errors in async functions for you and reports them as HTTP 500 Internal Server Errors.

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

app.get('*', async function(request, reply) {
  await new Promise(resolve => setTimeout(resolve, 100));
  throw new Error('Oops!');
});

await app.listen(3000);
try {
  await superagent.get('http://localhost:3000/');
} catch (error) {
  // Fastify reports the async error. Express would hang forever.
  assert.equal(error.response.body.message, 'Oops!');
}

Edge Cases

Once you call the reply.send() function, any subsequent errors in your async function will not be reported.

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

app.get('*', async function(request, reply) {
  await new Promise(resolve => setTimeout(resolve, 100));
  reply.send('Hello, World!');
  // This error won't get reported, the HTTP response will be "Hello, World!"
  throw new Error('Oops!');
});

await app.listen(3000);

If you're using Fastify with async/await, you should return data from the function body instead calling reply.send() to prevent potential silent errors. After you return from an async function, the async function body can't throw an error.

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

app.get('*', async function(request, reply) {
  await new Promise(resolve => setTimeout(resolve, 100));
  const response = { text: 'Hello, World!' };
  // Equivalent to `reply.send(JSON.stringify(response))`
  return response;
});

await app.listen(3000);

Moving On

In addition to having better performance on benchmarks, Fastify has better support for async/await than Express. Failure to handle async errors is a common problem for JavaScript frameworks, so I'm impressed that Fastify handles them correctly. Give Fastify a shot if you're looking for a server framework that supports async/await out of the box.

Want to learn how to identify whether your favorite npm modules work with async/await without resorting to Google or Stack Overflow? Chapter 4 of my new ebook, Mastering Async/Await, explains the basic principles for determining whether frameworks like React and Socket.IO support async/await. Get your copy!

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus