javascript
37 lines · 7 steps
Async error handling in Express routes
A small wrapper forwards rejected promises to Express's error middleware so async route handlers stay clean.
Explained by
highlit
1const asyncHandler = (fn) => (req, res, next) => {
2 Promise.resolve(fn(req, res, next)).catch(next);
3};
4
5const router = require('express').Router();
6const { User, Order } = require('../models');
7const { NotFoundError } = require('../errors');
8
9router.get('/users/:id/orders', asyncHandler(async (req, res) => {
10 const user = await User.findByPk(req.params.id);
11 if (!user) {
12 throw new NotFoundError(`User ${req.params.id} not found`);
13 }
14
15 const orders = await Order.findAll({
16 where: { userId: user.id, status: req.query.status ?? 'active' },
17 order: [['createdAt', 'DESC']],
18 limit: 50,
19 });
20
21 res.json({
22 user: { id: user.id, email: user.email },
23 orders: orders.map((o) => o.toJSON()),
24 });
25}));
26
27router.post('/users/:id/orders', asyncHandler(async (req, res) => {
28 const order = await Order.create({
29 userId: req.params.id,
30 items: req.body.items,
31 total: req.body.total,
32 });
33
34 res.status(201).json(order);
35}));
36
37module.exports = router;
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Express doesn't catch rejected promises from async handlers, so an unhandled rejection silently hangs the request unless you forward it to next.
- 2A tiny higher-order wrapper removes repetitive try/catch from every route while centralizing error handling.
- 3Throwing typed errors inside handlers lets one error middleware translate them into consistent HTTP responses.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/async-error-handling-in-express-routes-explained-javascript-0d59/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.