Lesson 19-Next.js Middleware System

Middleware Implementation and Mechanics

Creating Middleware

Middleware files should be placed in the pages/api/middleware directory of your project. If the directory does not exist, you need to create it manually.

mkdir pages/api/middleware

Writing Middleware

A middleware file is a standard JavaScript file that accepts a handler function as a parameter and returns a new handler function.

// pages/api/middleware/log.js
export default function log(handler) {
  return async function (req, res, next) {
    console.log(`Handling request for ${req.url}`);
    await handler(req, res, next);
  };
}

Using Middleware

To use middleware, import it into an API route file and apply it to the route handler function.

// pages/api/hello.js
import log from '../../../middleware/log';

export default log(async (req, res) => {
  res.json({ text: 'Hello Next.js!' });
});

How Middleware Works

A middleware function accepts three parameters: req (request object), res (response object), and next (callback function). Middleware can perform any operation and then call next() to pass control to the next middleware or route handler.

Example: Logging Middleware

Below is a simple logging middleware that records the request timestamp and method.

// pages/api/middleware/log.js
export default function log(handler) {
  return async function (req, res, next) {
    const start = Date.now();
    await handler(req, res, next);
    const end = Date.now();
    console.log(`${req.method} ${req.url} - ${end - start}ms`);
  };
}

Example: Error Handling Middleware

An error handling middleware to catch unhandled errors and return appropriate HTTP status codes and error messages.

// pages/api/middleware/error.js
export default function error(handler) {
  return async function (req, res, next) {
    try {
      await handler(req, res, next);
    } catch (err) {
      console.error(err);
      res.status(500).json({ message: err.message });
    }
  };
}

Example: Authentication Middleware

A simple authentication middleware to verify whether a user has permission to access a specific resource.

// pages/api/middleware/auth.js
export default function auth(handler) {
  return async function (req, res, next) {
    const requiredRole = req.query.role || 'user';
    const userRole = req.headers['x-user-role'] || 'guest';

    if (userRole !== requiredRole) {
      res.status(403).json({ message: 'Forbidden' });
      return;
    }

    await handler(req, res, next);
  };
}

Using Multiple Middleware

Multiple middleware can be used in a single API route file by chaining them.

// pages/api/hello.js
import log from '../../../middleware/log';
import auth from '../../../middleware/auth';
import error from '../../../middleware/error';

export default error(
  auth(
    log(async (req, res) => {
      res.json({ message: 'Hello, World!' });
    })
  )
);

Middleware Execution Order

Middleware executes in the order specified in the API route file. If a middleware does not call next(), subsequent middleware and the route handler will not be executed.

Advanced Middleware Usage

  • Dynamic Middleware: Dynamically select different middleware based on the request URL.
  • Conditional Middleware: Execute middleware based on specific conditions (e.g., request headers or query parameters).

Advanced Middleware Applications

Dynamic Middleware

Suppose we have two middleware: one for logging and another for authentication. We can dynamically choose which middleware to use based on the request URL.

// pages/api/middleware/index.js
import log from './log';
import auth from './auth';

export function selectMiddleware(url) {
  if (url.startsWith('/api/private')) {
    return auth;
  } else {
    return log;
  }
}

export default function dynamicMiddleware(handler) {
  return async function (req, res, next) {
    const middleware = selectMiddleware(req.url);
    await middleware(handler)(req, res, next);
  };
}

Conditional Middleware

Suppose we need a middleware that only executes when the request includes a specific header.

// pages/api/middleware/conditional.js
export default function conditionalMiddleware(handler) {
  return async function (req, res, next) {
    if (req.headers['x-enable-middleware']) {
      await handler(req, res, next);
    } else {
      next();
    }
  };
}

Using Dynamic and Conditional Middleware

Apply dynamic and conditional middleware in an API route file.

// pages/api/hello.js
import dynamicMiddleware from '../../../middleware/index';
import conditionalMiddleware from '../../../middleware/conditional';

export default conditionalMiddleware(
  dynamicMiddleware(async (req, res) => {
    res.json({ message: 'Hello, World!' });
  })
);

Middleware Error Handling

Error handling in middleware is crucial as it can impact the entire application’s behavior. Typically, a dedicated middleware is used for error handling.

// pages/api/middleware/error.js
export default function errorMiddleware(handler) {
  return async function (req, res, next) {
    try {
      await handler(req, res, next);
    } catch (error) {
      console.error(error);
      res.status(500).json({ message: 'Internal Server Error' });
    }
  };
}

Combining Middleware

Multiple middleware can be combined to achieve more complex functionality.

// pages/api/hello.js
import dynamicMiddleware from '../../../middleware/index';
import conditionalMiddleware from '../../../middleware/conditional';
import errorMiddleware from '../../../middleware/error';

export default errorMiddleware(
  conditionalMiddleware(
    dynamicMiddleware(async (req, res) => {
      res.json({ message: 'Hello, World!' });
    })
  )
);

Debugging Middleware

When debugging middleware, logging can help identify issues.

// pages/api/middleware/log.js
export default function logMiddleware(handler) {
  return async function (req, res, next) {
    console.log(`Handling request for ${req.url}`);
    await handler(req, res, next);
    console.log(`Request for ${req.url} completed`);
  };
}
Share your love