docs
Next JS
Middleware Chaning

MiddlewareChain Class Documentation

Overview

The MiddlewareChain class provides a modular, chainable approach to managing middleware in Next.js projects. It allows developers to define multiple middleware handlers, associate them with specific routes, and execute them sequentially based on incoming requests.


Basic Middleware Setup in Next.js

What is Middleware in Next.js?

Middleware in Next.js is a feature that allows you to run logic before a request is completed. It intercepts requests and can modify the response or redirect users.

Setting Up Middleware

  1. Create a middleware.ts File: In your Next.js project, create a middleware.ts file in the root directory. This file will act as the entry point for middleware logic.

  2. Structure: Use the MiddlewareChain class to manage and chain middleware logic.


MiddlewareChain Features

  • Chainable API: Add multiple middleware handlers using a simple, fluent API.
  • Path Matching: Apply middleware logic to specific routes or groups of routes.
  • Error Handling: Catches errors during execution and ensures consistent responses.
  • Integration: Built for Next.js NextRequest and NextResponse.

Installation and Setup

  1. Install Dependencies: Ensure your Next.js project is set up and dependencies are installed:

    npm install next
  2. Create MiddlewareChain: Create a file (e.g., middlewareChain.ts) and implement the MiddlewareChain class.


MiddlewareChain Class

Implementation

import { MiddlewareConfig } from "@/types/middleware";
import { NextRequest, NextResponse } from "next/server";
 
export class MiddlewareChain {
  private middlewares: (MiddlewareConfig & { handler: (request: NextRequest) => Promise<NextResponse> | NextResponse })[] = [];
 
  add(config: MiddlewareConfig & { handler: (request: NextRequest) => Promise<NextResponse> | NextResponse }): MiddlewareChain {
    this.middlewares.push(config);
    return this;
  }
 
  async execute(request: NextRequest): Promise<NextResponse> {
    for (const { matcher, handler } of this.middlewares) {
      if (this.matchPath(request.nextUrl.pathname, matcher)) {
        try {
          return await handler(request);
        } catch (error) {
          console.error('Middleware execution failed:', error);
          return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
        }
      }
    }
    return NextResponse.next();
  }
 
  private matchPath(pathname: string, matcher: string | string[]): boolean {
    return Array.isArray(matcher) ? matcher.some(path => pathname.startsWith(path)) : pathname.startsWith(matcher);
  }
}

Methods

add(config: MiddlewareConfig & { handler: (request: NextRequest) => Promise<NextResponse> | NextResponse }): MiddlewareChain

Adds middleware with a matcher and handler.

  • Parameters:
    • matcher: String or array of strings for route matching.
    • handler: Function to process the request and return a NextResponse.
  • Returns:
    • The current MiddlewareChain instance.

execute(request: NextRequest): Promise<NextResponse>

Processes the incoming request and runs matching middleware.

  • Parameters:
    • request: An instance of NextRequest.
  • Returns:
    • A NextResponse or proceeds to the next handler.

matchPath(pathname: string, matcher: string | string[]): boolean

Checks if the pathname matches the given matcher.


Usage

Middleware Setup with MiddlewareChain

import { MiddlewareChain } from './MiddlewareChain';
import { NextRequest, NextResponse } from 'next/server';
 
const middlewareChain = new MiddlewareChain();
 
middlewareChain
  .add({
    matcher: '/api/private',
    handler: async (request: NextRequest) => {
      const isAuthenticated = checkAuth(request); // Custom authentication logic
      if (!isAuthenticated) {
        return NextResponse.redirect(new URL('/login', request.url));
      }
      return NextResponse.next();
    },
  })
  .add({
    matcher: ['/api/admin', '/api/user'],
    handler: (request: NextRequest) => {
      const hasPermission = checkPermission(request); // Custom permission logic
      if (!hasPermission) {
        return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
      }
      return NextResponse.next();
    },
  });

Using MiddlewareChain in middleware.ts

import { middlewareChain } from './middlewareChain';
import { NextRequest } from 'next/server';
 
export async function middleware(request: NextRequest) {
  return await middlewareChain.execute(request);
}

Example Workflow

  1. Create Middleware Handlers: Define specific middleware handlers for authentication, logging, and other use cases.

  2. Chain Middleware: Use the add() method to chain middlewares with specific matchers.

  3. Integrate with Next.js: Call middlewareChain.execute(request) in the middleware.ts file.


Error Handling

If an error occurs during middleware execution:

  • Logs the error in the console.
  • Returns a 500 Internal Server Error response with a JSON payload:
    {
      "error": "Internal Server Error"
    }

Benefits

  1. Modular Design: Organize middleware logic by responsibility.
  2. Improved Readability: Manage complex routing and middleware logic easily.
  3. Reusable Components: Middlewares can be reused across projects.
  4. Robust Error Handling: Ensure consistent responses in case of failures.

Notes

  • Ensure matchers are specific to avoid unintended middleware execution.
  • Always return NextResponse from handlers for predictable behavior.
  • Use NextResponse.next() for default request processing.

For more information, visit the GitHub Repository (opens in a new tab).