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:
Register an account.
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.
Comments