top of page
Writer's pictureCODING Z2M

Effortless Error Handling in Node.js REST APIs: Using asyncHandler with Express


In a Node.js REST API, asynchronous functions are often used in controllers to interact with a database or external services. Without proper error handling, uncaught exceptions in async functions can cause the application to crash. The express-async-handler library simplifies error handling for async functions by automatically catching errors and forwarding them to Express's error-handling middleware.


'asyncHandler' is a middleware function that simplifies error handling for asynchronous functions in Node.js REST APIs. The asyncHandler middleware function is used to wrap asynchronous functions and handle any errors they throw. It catches the error, creates an HTTP response with the appropriate status code and error message, and passes it to the next error-handling middleware function in the Express stack.

Here's an example of how to use asyncHandler in a Node.js REST API:

​import express from 'express';
import asyncHandler from 'express-async-handler';
import { getProductById } from '../controllers/productController.js';
const router = express.Router();
router.get('/:id', asyncHandler(getProductById));

In this example, the getProductById function is an asynchronous function that retrieves a product by ID from a database. Instead of wrapping the function in a try-catch block and handling errors explicitly, we wrap it with the asyncHandler function to handle errors automatically.

If an error is thrown by the getProductById function, asyncHandler will catch the error, create an HTTP response with the appropriate status code and error message, and pass it to the next error-handling middleware function in the Express stack. This simplifies error handling and makes the code cleaner and easier to read.


Real-World Example

Scenario

Imagine an e-commerce application where users can:

  1. Register an account.

  2. Retrieve details of a specific product.

We will demonstrate using:

  • A registerUser method to handle user registration.

  • A getProductDetails method to fetch product details.


Install Required Libraries

npm install express mongoose express-async-handler

Folder Structure my-ecommerce-app/

├── controllers/

│ ├── userController.js

│ ├── productController.js

├── routes/

│ ├── userRoutes.js

│ ├── productRoutes.js

├── models/

│ ├── userModel.js

│ ├── productModel.js

├── middleware/

│ ├── errorMiddleware.js

├── server.js


Step 1. Create Models:

User Model (models/userModel.js)

const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
    name: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    password: { type: String, required: true },
}, { timestamps: true });
module.exports = mongoose.model('User', userSchema);

Product Model (models/productModel.js)

const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
    name: { type: String, required: true },
    price: { type: Number, required: true },
    description: { type: String, required: true },
}, { timestamps: true });
module.exports = mongoose.model('Product', productSchema);

Step 2. Create Controllers

User Controller (controllers/userController.js)

const User = require('../models/userModel');
// Async function for registering a user
const registerUser = async (req, res) => {
    const { name, email, password } = req.body;
    // Check if user already exists
    const existingUser = await User.findOne({ email });
    if (existingUser) {
        return res.status(400).json({ message: 'User already exists' });
    }
    // Create a new user
    const user = await User.create({ name, email, password });
    res.status(201).json({ message: 'User registered successfully', user });
};
module.exports = { registerUser };

Product Controller (controllers/productController.js)

const Product = require('../models/productModel');
// Async function for fetching product details
const getProductDetails = async (req, res) => {
    const productId = req.params.id;
    const product = await Product.findById(productId);
    if (!product) {
        return res.status(404).json({ message: 'Product not found' });
    }
    res.status(200).json(product);
};
module.exports = { getProductDetails };

Step 3. Define Routes

User Routes (routes/userRoutes.js)

const express = require('express');
const asyncHandler = require('express-async-handler');
const { registerUser } = require('../controllers/userController');
const router = express.Router();
router.post('/register', asyncHandler(registerUser));
module.exports = router;

Product Routes (routes/productRoutes.js)

const express = require('express');
const asyncHandler = require('express-async-handler');
const { getProductDetails } = require('../controllers/productController');
const router = express.Router();
router.get('/:id', asyncHandler(getProductDetails));
module.exports = router;


Step 4. Error-Handling Middleware

Error Middleware (middleware/errorMiddleware.js)

const errorHandler = (err, req, res, next) => {
    console.error(err.stack); //err.stack is a string property of the Error object, which contains the stack trace
    res.status(500).json({ message: err.message || 'Server error' });
};
module.exports = errorHandler;


Step 5. Set Up Express Server

Server File (server.js)

const express = require('express');
const mongoose = require('mongoose');
const userRoutes = require('./routes/userRoutes');
const productRoutes = require('./routes/productRoutes');
const errorHandler = require('./middleware/errorMiddleware');
require('dotenv').config();
const app = express();
// Middleware
app.use(express.json());
// Routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);
// Error handling middleware
app.use(errorHandler);
// Connect to MongoDB and start the server
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => {
        const PORT = process.env.PORT || 5000;
        app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
    })
    .catch(err => console.log(err));


How This Works

  • Async Controllers: The registerUser and getProductDetails methods are async functions.

  • Error Handling with asyncHandler:

    • If any unhandled exception occurs (e.g., database errors, invalid requests), asyncHandler catches it and forwards it to the errorHandler middleware.

    • This removes the need for repetitive try-catch blocks.

  • Centralized Error Handling:

    • Errors are handled in one place (errorMiddleware.js), improving maintainability and consistency.

  • Clean Code: The controllers are focused only on business logic, while error handling is managed globally.



Example API Requests

Register User

POST /api/users/register

Content-Type: application/json

{
    "name": "John Doe",
    "email": "john@example.com",
    "password": "password123"
}

Get Product Details

GET /api/products/63e88f2a1b20d0034a1b4567


If the product ID is invalid, a 404 Product not found error is returned. If there's a server error, a 500 Server error response is sent.

This modular and clean approach ensures that errors are handled gracefully in a scalable Node.js REST API.

0 views0 comments

Recent Posts

See All

Comments


bottom of page