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)