# 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:

```js
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`

```js
// 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:

```js
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`.

---

## 📦 Option 2: Use a Library (Highly Recommended)

### 🔹 express-error-toolkit — [View on npm](https://www.npmjs.com/package/express-error-toolkit)

![npm](https://img.shields.io/npm/v/express-error-toolkit)
![downloads](https://img.shields.io/npm/dm/express-error-toolkit)

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

```bash
npm install express-error-toolkit
```

#### Use

```js
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:

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

module.exports = ApiError;
```

Usage:

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

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

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

```js
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:

```js
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:

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

app.use(globalErrorHandler);
```

---

## 🧪 Full Example

```js
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. 🚀
