Skip to main content
Practice Problems

How to handle async/await in Express.js route handlers?

Async/Await in Express.js

Express was designed before async/await existed. By default, Express does not catch unhandled Promise rejections from async route handlers β€” they crash your app silently or cause unhandled rejection warnings.


The Problem

js
// ❌ BROKEN β€” unhandled Promise rejection! app.get('/users', async (req, res) => { const users = await User.findAll(); // throws? Express doesn't catch it! res.json(users); });

If User.findAll() rejects, the error is not passed to Express's error handler.


Solution 1: try/catch + next(err)

js
// βœ… Correct β€” but verbose app.get('/users', async (req, res, next) => { try { const users = await User.findAll(); res.json(users); } catch (err) { next(err); // forward to error handler } });

This works but writing try/catch in every handler is tedious.


Solution 2: asyncHandler Wrapper

js
// utils/asyncHandler.js const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; module.exports = asyncHandler; // Clean route handlers! const asyncHandler = require('../utils/asyncHandler'); app.get('/users', asyncHandler(async (req, res) => { const users = await User.findAll(); res.json(users); })); app.post('/users', asyncHandler(async (req, res) => { const user = await User.create(req.body); res.status(201).json(user); }));

Solution 3: express-async-errors (Monkey-Patch)

bash
npm install express-async-errors
js
// Import ONCE at the top of app.js require('express-async-errors'); // Now async errors are forwarded to next() automatically! app.get('/users', async (req, res) => { const users = await User.findAll(); // if throws β†’ goes to error handler res.json(users); });

This patches Express internally. No wrapper needed.


Solution 4: Express 5 (Automatic)

Express 5 (currently RC) handles async errors natively:

bash
npm install express@next
js
// Express 5 β€” async errors automatically forwarded to next() app.get('/users', async (req, res) => { const users = await User.findAll(); res.json(users); });

Parallel Async Operations

js
app.get('/dashboard', asyncHandler(async (req, res) => { // ❌ Sequential β€” slow! (each awaits the previous) const users = await fetchUsers(); const posts = await fetchPosts(); const stats = await fetchStats(); // βœ… Parallel β€” all run simultaneously const [users, posts, stats] = await Promise.all([ fetchUsers(), fetchPosts(), fetchStats() ]); res.json({ users, posts, stats }); }));

Timeout Handling

js
function withTimeout(promise, ms = 5000) { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error(`Request timed out after ${ms}ms`)), ms) ); return Promise.race([promise, timeout]); } app.get('/slow', asyncHandler(async (req, res) => { const data = await withTimeout(slowExternalAPI(), 3000); res.json(data); }));

Best Practices

  1. Always use asyncHandler wrapper OR express-async-errors
  2. Never swallow errors β€” always call next(err) or let the wrapper do it
  3. Use Promise.all() for independent async operations (faster)
  4. Set timeouts for external API calls
  5. Use Express 5 for new projects β€” native async support

Summary

Express doesn't automatically handle async errors. Use an asyncHandler wrapper, express-async-errors, or migrate to Express 5 to safely use async/await in your route handlers. Always pass errors to next(err) so your central error handler can process them.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems