Skip to main content

Command Palette

Search for a command to run...

Say Goodbye to Try-Catch: Smarter Async Error Handling in Express

Updated
3 min read
Say Goodbye to Try-Catch: Smarter Async Error Handling in Express

When building a backend with Node.js and Express, we're likely using async/await to handle things like database queries or API calls.

But there’s a catch — if we don’t handle errors properly, our server can crash or behave unpredictably. 😬

In this post, you'll learn a clean, scalable way to handle async errors in Express:

  • Why try-catch in every route is painful
  • How to fix it with a reusable asyncHandler()
  • How to simplify this using external libraries
  • How to use my own package: express-error-toolkit
  • How to define custom error classes
  • And how to set up a global error handler

🚨 The Problem With Try-Catch Everywhere

Here’s how we usually handle errors:

app.get('/api/users/:id', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) return res.status(404).json({ message: 'User not found' });
    res.json(user);
  } catch (error) {
    next(error);
  }
});

Repeating this in every route is:

  • Redundant
  • Ugly
  • Easy to forget

Let’s fix that.


✅ Option 1: Write a Custom asyncHandler

// utils/asyncHandler.js
const asyncHandler = (fn) => {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

module.exports = asyncHandler;

Use it like this:

const asyncHandler = require('../utils/asyncHandler');

app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) throw new Error('User not found');
  res.json(user);
}));

Clean. Reusable. No try-catch.


🔹 express-error-toolkit — View on npm

npm downloads

I built this package to make error handling in Express apps much easier. It includes:

  • An asyncHandler() function
  • Predefined error classes (NotFoundError, BadRequestError, etc.)
  • A global error-handling middleware
  • Clean stack traces in development

Install

npm install express-error-toolkit

Use

const { asyncHandler } = require('express-error-toolkit');

app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) throw new Error('User not found');
  res.json(user);
}));

🧱 Define Custom Error Classes

If you don’t use a package, you can define your own:

// utils/ApiError.js
class ApiError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = ApiError;

Usage:

const ApiError = require('../utils/ApiError');

if (!user) throw new ApiError(404, 'User not found');

Or use express-error-toolkit’s built-in errors

const { NotFoundError } = require('express-error-toolkit');

if (!user) throw new NotFoundError('User not found');

🌍 Global Error-Handling Middleware

Add this at the end of your middleware chain:

app.use((err, req, res, next) => {
  const status = err.statusCode || 500;
  const message = err.message || 'Internal Server Error';

  res.status(status).json({
    success: false,
    message,
    stack: process.env.NODE_ENV === 'production' ? null : err.stack,
  });
});

Or use express-error-toolkit’s built-in handler:

const { globalErrorHandler } = require('express-error-toolkit');

app.use(globalErrorHandler);

🧪 Full Example

const express = require('express');
const mongoose = require('mongoose');
const {
  NotFoundError,
  asyncHandler,
  globalErrorHandler,
} = require('express-error-toolkit');

const app = express();
app.use(express.json());

app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) throw new NotFoundError('User not found');
  res.json(user);
}));

app.use(globalErrorHandler);

app.listen(3000, () => console.log('Server running on port 3000'));

🧠 Final Thoughts

✅ Avoid try-catch in every route using asyncHandler
📦 Use express-error-toolkit for a full-featured, clean setup
🧱 Throw meaningful errors with custom classes
🌍 Catch and format all errors in one global middleware

Follow this approach and your Express backend will be clean, scalable, and production-ready. 🚀

More from this blog

R

Rashedin’s Dev Diary

8 posts

A personal space where code meets creativity—sharing my journey, insights, and reflections on programming to inspire fellow developers and curious minds alike.