DEV Community

Cover image for Day 43 of #100DaysOfCode — Error Handling + Async Wrapper
M Saad Ahmad
M Saad Ahmad

Posted on

Day 43 of #100DaysOfCode — Error Handling + Async Wrapper

Error handling is crucial in backend development. Even well-written APIs can encounter issues, such as invalid data from users, non-existent resources, or failures from external services like databases. Effective error handling allows applications to respond with clear and consistent messages, rather than crashing or revealing sensitive information.

Good backend systems manage errors gracefully by using meaningful messages and appropriate HTTP status codes. Thus, understanding error handling is essential for creating reliable and professional APIs.

Today, for Day 43, the goal was to understand how error handling works and how errors are efficiently handled in the backend.


Types of Errors in Backend Applications

In backend development, errors usually fall into three main categories.

Understanding these helps you design clean API responses and debugging strategies.

1. Operational Errors

These are expected errors during normal application usage.

Examples:

User not found
Invalid password
Validation failed
Unauthorized access
Enter fullscreen mode Exit fullscreen mode

These errors are not bugs.
They should return clean and meaningful responses to the client.

Example API response:

{
  "success": false,
  "message": "User not found"
}
Enter fullscreen mode Exit fullscreen mode

Common operational errors:

  • Invalid input
  • Authentication failure
  • Authorization issues
  • Missing resources

2. Programming Errors

These are developer mistakes (bugs).

Examples:

undefined variable
incorrect logic
null reference
Enter fullscreen mode Exit fullscreen mode

These indicate something is wrong in the code itself.

Programming errors usually require fixing the code, not returning them directly to users.

3. System Errors

System errors come from external systems or infrastructure.

Examples:

database connection failure
file system error
network timeout
third-party API failure
Enter fullscreen mode Exit fullscreen mode

These are often outside your application's control.

Good APIs handle them gracefully without crashing the server.


Default Express Error Handling

In Express, errors must be passed to the next middleware using next().

Example:

app.get("/user", (req, res, next) => {
  try {
    throw new Error("Something went wrong");
  } catch (error) {
    next(error);
  }
});
Enter fullscreen mode Exit fullscreen mode

Calling next(error) tells Express:

"An error occurred — send it to the error handling middleware."


Global Error Handling Middleware

Instead of handling errors inside every route, production APIs use a centralized error handler.

Example:

app.use((err, req, res, next) => {
  res.status(500).json({
    success: false,
    message: err.message
  });
});
Enter fullscreen mode Exit fullscreen mode

Important concept:

Error middleware must have four parameters:

(err, req, res, next)
Enter fullscreen mode Exit fullscreen mode

This signature tells Express that this middleware is specifically for handling errors.


Standardized Error Responses

Good APIs always return consistent error responses.

Example structure:

{
  "success": false,
  "message": "Resource not found"
}
Enter fullscreen mode Exit fullscreen mode

Or:

{
  "success": false,
  "error": "Unauthorized access"
}
Enter fullscreen mode Exit fullscreen mode

Best practices:

  • consistent structure
  • meaningful error messages
  • correct HTTP status codes
  • no sensitive information in production

Common status codes:

Status Code Meaning
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error

Custom Error Classes

Instead of using the generic Error, we can create custom error classes.

Example:

class AppError extends Error {
  constructor(message, statusCode) {
    super(message)
    this.statusCode = statusCode
  }
}
Enter fullscreen mode Exit fullscreen mode

Conceptual structure:

Error
  └── AppError
Enter fullscreen mode Exit fullscreen mode

Example usage:

throw new AppError("User not found", 404)
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • attach HTTP status codes
  • cleaner error handling
  • better debugging
  • consistent API responses

Async Problems in Express

Most modern backend controllers are asynchronous.

Example:

app.get("/users", async (req, res) => {
  const users = await User.find()
  res.json(users)
})
Enter fullscreen mode Exit fullscreen mode

Problem in Express 4:

If an error occurs inside an async function, Express does not automatically catch it.

Example:

UnhandledPromiseRejectionWarning
Enter fullscreen mode Exit fullscreen mode

This can crash your server if not handled properly.

(Note: Express 5 improves this behavior.)


The Try/Catch Problem

A common solution is using try/catch.

Example:

app.get("/users", async (req, res, next) => {
  try {
    const users = await User.find()
    res.json(users)
  } catch (error) {
    next(error)
  }
})
Enter fullscreen mode Exit fullscreen mode

But repeating try/catch in every controller becomes messy and hard to maintain.


The Async Wrapper Pattern

A better solution is using an Async Wrapper.

Instead of writing try/catch everywhere, we create a reusable function.

Concept:

asyncWrapper(fn)
Enter fullscreen mode Exit fullscreen mode

Usage:

asyncWrapper(getUsers)
Enter fullscreen mode Exit fullscreen mode

Example:

function asyncWrapper(fn) {
  return function(req, res, next) {
    fn(req, res, next).catch(next)
  }
}
Enter fullscreen mode Exit fullscreen mode

The wrapper automatically catches errors and forwards them to the global error handler.

Benefits:

  • cleaner controllers
  • reusable logic
  • widely used in production APIs

How the Async Wrapper Works

Example implementation:

function asyncWrapper(fn) {
  return function (req, res, next) {
    Promise.resolve(fn(req, res, next)).catch(next)
  }
}
Enter fullscreen mode Exit fullscreen mode

Example usage:

router.get("/users", asyncWrapper(getUsers))
Enter fullscreen mode Exit fullscreen mode

Now your controller can stay clean:

const getUsers = async (req, res) => {
  const users = await User.find()
  res.json(users)
}
Enter fullscreen mode Exit fullscreen mode

No try/catch needed.


Error Flow in a Professional API

In well-structured APIs, the error flow typically looks like this:

Route
   ↓
Async Wrapper
   ↓
Controller
   ↓
Error Occurs
   ↓
Global Error Handler
   ↓
Client Response
Enter fullscreen mode Exit fullscreen mode

This architecture keeps the backend code:

  • clean
  • scalable
  • maintainable

🧠 Key Takeaways

Error handling is a core skill in backend development.

Important concepts to remember:

  • understand operational vs programming errors
  • use global error middleware
  • create custom error classes
  • avoid repeating try/catch
  • use async wrapper pattern

Learning backend is not just about making APIs work. It's about making them reliable, maintainable, and production-ready. Error handling is one of the foundations of professional backend development. These patterns are used in real production Node.js APIs.

Thanks for reading. Feel free to share your thoughts!

Top comments (0)