~/TechPurAI
~/tutorials/build-a-rest-api/error-handling
beginner·part 3 of 6·2 min read

Error handling in Express, the way you'll actually use it

Updated Aug 31, 2026Node.js · Express

By now your API can respond to requests, but it falls over the moment something goes wrong — a missing field, a database timeout, a typo in a route param. This part fixes that with a single error-handling middleware you'll reuse in every project after this one.

Why default error handling fails

Express catches synchronous errors automatically, but throws them as raw stack traces straight to the client. That's fine on your machine and a liability in production — it can leak file paths, package versions, and internal logic to anyone who trips an error.

Heads up

If you skip this step, every unhandled error in your API returns a raw HTML stack trace to the caller — including in production. This is the single most common issue we see in first APIs.

Writing an error middleware

Express treats any middleware with four arguments as an error handler. Add this after all your routes, and it becomes the last stop for anything that goes wrong:

js
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  const status = err.status || 500;
  res.status(status).json({
    error: err.message || 'Something went wrong',
  });
}

// register it last, after every route
app.use(errorHandler);

Handling async errors

The middleware above only catches synchronous throws. Since most of your route handlers will be async, wrap them so rejected promises get forwarded to the same handler instead of hanging the request:

js
const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await db.users.find(req.params.id);
  res.json(user);
}));

Try it yourself

Add the error handler to your project, then intentionally throw an error inside a route to confirm you get a clean JSON response instead of a stack trace. In the next part, you'll connect a real database and see these handlers catch actual failures.

FAQ

Why four arguments — doesn't next go unused? Express specifically detects an error-handling middleware by counting its parameters — a function with exactly four (err, req, res, next) is treated as an error handler, even if next is never called inside it. Removing any one of the four (including the unused next) means Express treats it as a normal middleware instead, and it silently never catches anything.

Does this catch errors from middleware, or only from routes? Both — any middleware or route handler that calls next(err), or that throws synchronously, reaches this handler as long as it's registered after everything else with app.use(errorHandler).

Should I ever send err.stack back to the client? No, not in production — the callout above exists specifically because a stack trace can reveal file paths and internal package versions. Logging err.stack on the server (as this middleware does with console.error) while sending only err.message to the client is the right split.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous2. Your first routenext →4. Connecting a database