In Express.js apps, especially when dealing with async operations (like database queries), you've likely repeated the same code structure over and over:
const getAllUsers = async (req, res, next) => {
try {
const users = await UserServices.getAllUsers();
res.status(200).json({ users });
} catch (error) {
next(error);
}
};
✅ This works — but do you really want to write that try...catch
block every single time?
Let’s clean it up using a Higher-Order Function.
🚫 The Problem
- Every controller duplicates the same
try-catch-next()
logic. - It violates the DRY principle (Don't Repeat Yourself).
- It clutters your code and distracts from the actual business logic.
✅ The Solution: catchAsync
Let’s abstract the error-catching logic into a reusable utility function.
📁 src/utils/catchAsync.ts
import { NextFunction, Request, RequestHandler, Response } from 'express';
export const catchAsync = (fn: RequestHandler) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch((err) => next(err));
};
};
🧠 What's Going On Here? What Is a Higher-Order Function (HOF)?
The catchAsync
function is a Higher-Order Function (HOF).
A Higher-Order Function is simply a function that:
- Takes another function as an argument, or
- Returns a new function, or
-
Does both (like
catchAsync
!)
🔁 Why are HOFs powerful?
- They allow you to wrap and enhance behavior without repeating code.
- You can build reusable utilities like authentication guards, validators, logging, and — in our case — async error handlers.
🔍 Deep Dive: How catchAsync
Works
const catchAsync = (fn: RequestHandler) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch((err) => next(err));
};
};
Let’s break this down:
Line | Explanation |
---|---|
catchAsync(fn) |
Takes your controller as an argument |
return (req, res, next) |
Returns a new function with the same signature Express expects |
Promise.resolve(...) |
Ensures even non-async handlers are wrapped in a Promise |
.catch((err) => next(err)) |
Automatically catches any error and forwards it to Express’s global error handler |
🧠 So instead of you writing try...catch
, catchAsync
does it for you!
🎯 Cleaner Controller Code
Now your controller becomes:
import { catchAsync } from '../../../utils/catchAsync';
export const getAllUsers = catchAsync(async (req, res, next) => {
const users = await UserServices.getAllUsers();
res.status(200).json({
success: true,
message: 'Users retrieved successfully',
data: users
});
});
✅ No more try...catch
clutter
✅ Just focus on the actual logic
💬 Bonus: Want to Handle Validation or Custom Errors?
You can combine catchAsync
with a custom error class like AppError
. You can read more about it here (https://dev.to/alifa_ara_heya/building-a-better-apperror-class-in-nodejs-for-robust-api-error-handling-25o)
if (!user) {
throw new AppError(404, 'User not found');
}
Now your global error handler can cleanly handle all cases.
🧪 TL;DR
Problem | Solution |
---|---|
Repeating try/catch in every controller | Use catchAsync() to wrap async functions |
Messy controllers | Cleaner, focused logic |
Manual error handling | Automatic forwarding to global error middleware |
🧠 Final Thoughts
Using Higher-Order Functions like catchAsync
makes your codebase:
- More readable
- Less error-prone
- Easier to maintain
It’s a small trick, but it will make your Express codebase much more professional and clean.
🔁 Try it out in your next Express project and say goodbye to repetitive try...catch
blocks forever!
#expressjs #javascript #nodejs #backend #cleancode #webdev #devtips
Top comments (0)