Day 5: Implementing Middleware and Error Handling


In this tutorial, you’ll learn how to create custom middleware and implement proper error handling in your Express API. Middleware functions can be used for logging requests, validating input data, and catching errors. We’ll also explore how to handle common HTTP errors in a consistent manner.


What You Will Learn Today:

  1. Creating custom middleware for logging
  2. Adding validation middleware using express-validator
  3. Implementing global error handling middleware
  4. Handling common HTTP errors (e.g., 404, 500)
  5. Testing middleware with Postman

Step 1: Creating a Logging Middleware

Let’s start by creating a middleware function to log incoming requests. This will help track which routes are being accessed and when.

Creating the Logger Middleware

  1. Inside your project directory, create a new folder called middleware (if you haven’t already), and create a file called logger.js:
touch middleware/logger.js
  1. Open middleware/logger.js and add the following logging middleware:
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next(); // Pass control to the next middleware or route handler
};

module.exports = logger;

Explanation:

  • req.method: Logs the HTTP method (GET, POST, etc.).
  • req.url: Logs the URL being accessed.
  • new Date().toISOString(): Logs the current timestamp in ISO format.
  • next(): Calls the next middleware function in the stack.
See also  Day 4: Handling User Input and Forms

Using the Logger Middleware

Now, let’s apply the logger middleware globally so it runs for all requests.

  1. Open index.js and add the logger middleware:
const express = require('express');
const mongoose = require('mongoose');
const logger = require('./middleware/logger');
const app = express();
const PORT = process.env.PORT || 5000;

// Middleware
app.use(logger); // Use the logger middleware for all routes
app.use(express.json()); // Parse incoming JSON data

// MongoDB connection
mongoose.connect('your_mongo_connection_string', { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected'))
  .catch((err) => console.log('MongoDB connection error:', err));

// Routes
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);

// Start the server
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Explanation:

  • We added the logger middleware globally using app.use(logger). This means every request made to the server will be logged.

Step 2: Adding Validation Middleware

Data validation is an important part of building APIs, ensuring that the data sent by the client meets the expected format and constraints. We’ll use express-validator to handle input validation.

Installing express-validator

  1. Install the express-validator package by running the following command:
npm install express-validator

Adding Validation to the User Registration Route

Let’s add validation to the user registration route to ensure that the name, email, and password fields are provided and meet certain criteria.

  1. Open routes/users.js and modify the /register route to include validation:
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { check, validationResult } = require('express-validator');
const router = express.Router();
const User = require('../models/User');

// Register a new user with validation
router.post(
  '/register',
  [
    check('name', 'Name is required').not().isEmpty(),
    check('email', 'Please include a valid email').isEmail(),
    check('password', 'Password must be at least 6 characters long').isLength({ min: 6 }),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { name, email, password } = req.body;

    try {
      const existingUser = await User.findOne({ email });
      if (existingUser) {
        return res.status(400).json({ message: 'User already exists' });
      }

      const hashedPassword = await bcrypt.hash(password, 10);
      const newUser = new User({ name, email, password: hashedPassword });
      const savedUser = await newUser.save();

      res.status(201).json(savedUser);
    } catch (error) {
      res.status(500).json({ message: 'Error registering user', error });
    }
  }
);

Explanation:

  • check(): This is used to define validation rules for specific fields. For example:
  • name must not be empty.
  • email must be a valid email address.
  • password must be at least 6 characters long.
  • validationResult(req): This checks if any validation errors occurred. If there are errors, it returns a 400 Bad Request response with the error messages.
See also  Day 4: Authentication Using JWT (JSON Web Tokens)

Step 3: Implementing Global Error Handling Middleware

To handle errors in a centralized manner, we’ll create a global error-handling middleware. This will catch any errors that occur during the execution of your route handlers and return consistent error messages to the client.

Creating an Error Handling Middleware

  1. Inside the middleware folder, create a new file called errorHandler.js:
touch middleware/errorHandler.js
  1. Open middleware/errorHandler.js and define the error-handling middleware:
const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Server error', error: err.message });
};

module.exports = errorHandler;

Explanation:

  • err.stack: Logs the error stack trace to the console for debugging.
  • res.status(500): Returns a 500 Internal Server Error response with a generic error message.

Using the Error Handler

  1. Open index.js and add the error-handling middleware at the bottom, after all routes:
const errorHandler = require('./middleware/errorHandler');

// Other middleware and routes here...

// Error handling middleware (should be the last middleware)
app.use(errorHandler);

Explanation:

  • The error handler is added after all routes and middleware. This ensures that it catches any errors that occur during request processing.

Step 4: Handling Common HTTP Errors (404 and Others)

In addition to server errors, we should handle 404 Not Found errors when a user tries to access a non-existent route.

Handling 404 Errors

  1. Add a route to catch all undefined routes and respond with a 404 error:
// Catch-all route for 404 errors
app.use((req, res, next) => {
  res.status(404).json({ message: 'Resource not found' });
});

Explanation:

  • This route catches any requests to undefined routes and returns a 404 Not Found response.

Step 5: Testing Middleware and Error Handling with Postman

Let’s test the middleware and error handling to ensure everything is working properly.

See also  Queues and Background Jobs in Laravel: A Comprehensive Guide

1. Testing the Logger Middleware

  1. Restart your server by running:
node index.js
  1. Open Postman and make a GET request to http://localhost:5000/api/users. In your terminal, you should see a log message similar to this:
GET /api/users - 2024-10-01T14:37:22.345Z

This confirms that the logger middleware is working.

2. Testing Validation Middleware

  1. Make a POST request to http://localhost:5000/api/users/register with the following invalid JSON body (no password):
{
  "name": "John Doe",
  "email": "[email protected]"
}
  1. You should receive a 400 Bad Request response with the validation errors:
{
  "errors": [
    { "msg": "Password must be at least 6 characters long", "param": "password", "location": "body" }
  ]
}

3. Testing Global Error Handling

  1. Make an invalid GET request to http://localhost:5000/api/users/invalid_id.
  2. You should receive a 500 Internal Server Error response with the generic error message.

4. Testing 404 Errors

  1. Make a GET request to a non-existent route, such as http://localhost:5000/api/unknown.
  2. You should receive a 404 Not Found response with the message:
{
  "message": "Resource not found"
}

Step 6: Recap and Summary

In this tutorial, you learned how to implement custom middleware and error handling in your

Express API. Here’s a quick summary of what you’ve done:

  • Created a logger middleware to log incoming requests.
  • Added validation middleware using express-validator to validate user input.
  • Implemented global error handling middleware to catch and respond to errors consistently.
  • Handled 404 errors for undefined routes.
  • Tested the middleware and error handling using Postman.

With these improvements, your API is now more robust and handles errors gracefully.


Next Up: Day 6 – Securing Your API with HTTPS and Environment Variables

In Day 6, we’ll focus on securing your API by using HTTPS, managing environment variables, and implementing security best practices.

Stay tuned for more advanced features tomorrow!


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.