Middleware in Express.js

Anjali SainiAnjali Saini
6 min read

Middleware in Express.js and Enhancing the Movie API Project

1. Introduction to Middleware

What is Middleware?

In Express, middleware functions are functions that execute during the request-response cycle, providing a way to modify requests and responses or handle specific tasks before they reach the main route handlers.

Why Use Middleware?

Middleware helps streamline and manage common tasks across your application:

  • Request Logging: Tracking incoming requests.

  • Security Enhancements: Adding headers or restricting access.

  • Data Validation and Parsing: Ensuring data integrity.

  • Error Handling: Managing errors consistently.

Types of Middleware in Express

  1. Application-level Middleware: Applied at the app level to manage all requests.

  2. Router-level Middleware: Applied only to specific routes or routers.

  3. Built-in Middleware: Provided by Express for common tasks.

  4. Third-party Middleware: Libraries installed from npm for specialized functions.

  5. Custom Middleware: Custom functions for specific logic in your application.


2. Middleware Types Explained with Examples

1. Application-level Middleware

Application-level middleware is applied to every request within an application. You define it using app.use().

app.use(express.json()); // Parses incoming JSON data
app.use(cors());         // Enables CORS to allow cross-origin requests

2. Router-level Middleware

Router-level middleware is used within specific routers, targeting only those routes. You define it using router.use().

const router = express.Router();
router.use((req, res, next) => {
  console.log('Router-level middleware');
  next();
});

3. Built-in Middleware

Express includes several built-in middleware functions:

  • express.json(): Parses JSON payloads in incoming requests.

  • express.urlencoded(): Parses URL-encoded data (like form submissions).

4. Third-party Middleware

Many npm packages provide middleware functionality:

  • CORS (cors): Controls cross-origin resource sharing.

  • Helmet (helmet): Adds security headers.

  • Morgan (morgan): Logs HTTP requests.

5. Custom Middleware

Custom middleware is written specifically to meet application needs. For example, a middleware to check if a user is authenticated.

const authMiddleware = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).send('Unauthorized');
  }
  next();
};

3. Movie Project Enhancements Using Middleware

Step 1: Application-Level Middleware

Enhance the Movie API project by adding application-level middleware for data size limits, security headers, CORS, and logging.

Setting Up Middleware in index.js

  1. Limit JSON Body Size and URL-encoded Body Size:

    • Use express.json() and express.urlencoded() to limit the maximum request body size.
    import express from 'express';
    import cors from 'cors';
    import helmet from 'helmet';
    import morgan from 'morgan';
    import movieRoutes from './routes/movies.js';

    const app = express();
    const PORT = 3000;

    // Limit JSON body size to 1MB
    app.use(express.json({ limit: '1mb' }));

    // Limit URL-encoded body size to 1MB
    app.use(express.urlencoded({ limit: '1mb', extended: true }));

    // CORS to allow requests only from the frontend
    app.use(cors({ origin: 'http://localhost:3000' }));

    // Security headers for all routes
    app.use(helmet());

    // HTTP request logging
    app.use(morgan('combined'));

    app.use('/movies', movieRoutes);

    app.listen(PORT, () => {
      console.log(`Server running on http://localhost:${PORT}`);
    });
  1. Explanation of Middleware Functions:

    • express.json({ limit: '1mb' }): Limits incoming JSON data to 1MB, protecting against payload abuse.

    • express.urlencoded({ limit: '1mb', extended: true }): Limits URL-encoded data (used in forms) to 1MB.

    • cors({ origin: 'http://localhost:3000' }): Restricts API access to your frontend’s origin.

    • helmet(): Adds security headers to prevent certain attacks (e.g., clickjacking, XSS).

    • morgan('combined'): Logs HTTP requests for monitoring.


Step 2: Router-Level Middleware

Let’s add a dummy authentication middleware to restrict access to the movie routes, simulating basic authentication.

  1. Dummy Authentication Middleware:

    • Create a middleware function that checks for an Authorization header with a dummy token.
    // routes/movies.js
    import express from 'express';
    import { getAllMovies, getSingleMovie, createMovie } from '../controllers/movieController.js';

    const router = express.Router();

    // Dummy Auth Middleware
    const dummyAuthMiddleware = (req, res, next) => {
      const authHeader = req.headers.authorization;
      if (authHeader === 'Bearer my-secret-token') {
        next();
      } else {
        res.status(401).json({ error: 'Unauthorized' });
      }
    };

    // Apply auth middleware to all movie routes
    router.use(dummyAuthMiddleware);

    router.get('/', getAllMovies);
    router.get('/:id', getSingleMovie);
    router.post('/', createMovie);

    export default router;
  1. Explanation:

    • dummyAuthMiddleware: Checks for an authorization header. If it’s missing or incorrect, a 401 Unauthorized response is sent.

    • This middleware simulates authentication and can be replaced with real auth logic as needed.


4. HTTP Status Codes and Error Handling Best Practices

Using correct HTTP status codes helps inform clients of the response type and any errors encountered. Here’s a quick reference:

  • 200 OK: Success.

  • 201 Created: Successfully created a new resource.

  • 400 Bad Request: Client error due to invalid data.

  • 401 Unauthorized: Access denied due to lack of authorization.

  • 404 Not Found: Resource not found.

  • 500 Internal Server Error: Server-side error.

Best Practices for Error Handling:

  • Return specific error messages for client-side errors.

  • Use try/catch for operations that might fail, such as file reads and writes.

  • Create a global error-handling middleware to handle unexpected errors.

Global Error-Handling Middleware

Define a global error handler as the last middleware in index.js:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

5. Full Movie API Project with Middleware Enhancements

Here’s the final structure and code for an enhanced Movie API project with all middleware integrated.

Project Structure

movie-api/
├── controllers/
│   └── movieController.js
├── routes/
│   └── movies.js
├── middleware/
│   └── dummyAuth.js           # Router-level auth middleware
├── db.json                     # JSON file serving as database
├── index.js                    # Main server file
└── package.json

Final index.js

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import movieRoutes from './routes/movies.js';

const app = express();
const PORT = 3000;

// Application-level middleware
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ limit: '1mb', extended: true }));
app.use(cors({ origin: 'http://localhost:3000' }));
app.use(helmet());
app.use(morgan('combined'));

// Routes
app.use('/movies', movieRoutes);

// Global error-handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

dummyAuth.js (Router-Level Middleware)

const dummyAuthMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (authHeader === 'Bearer my-secret-token') {
    next();
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
};

export default dummyAuthMiddleware;

movies.js (Route Definitions)

import express from 'express';
import { getAllMovies, getSingleMovie, createMovie } from '../controllers/movieController.js';
import dummyAuthMiddleware from '../middleware/dummyAuth.js';

const router = express.Router();

// Apply dummy auth middleware to all routes
router.use(dummyAuthMiddleware);

router.get('/', getAllMovies);
router.get('/:id', getSingleMovie);
router.post('/', createMovie);

export default router;

Summary

  • Middleware provides a way to handle repetitive tasks across requests, improving maintainability.

  • Types of middleware include application-level, router-level, built-in, third-party, and custom middleware.

  • In the Movie API project, we used application-level middleware for data size limits, security headers, CORS, and logging.

  • Router-level middleware, like dummy authentication, restricts access to specific routes.

  • HTTP status codes and structured error handling communicate response outcomes clearly.


0
Subscribe to my newsletter

Read articles from Anjali Saini directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anjali Saini
Anjali Saini

I am an Enthusiastic and self-motivated web-Developer . Currently i am learning to build end-to-end web-apps.