Medium15 minWeb Fundamentals
UpdatedAug 2, 2026
Edit

Custom Middleware

Question Variations

  • "What is middleware, and how does the request pipeline work in frameworks like Express or ASP.NET Core?"
  • "Can you describe a scenario where the order of middleware components would cause a bug?"
  • "How would you implement a custom middleware for global error handling?"
  • "How can middleware be used to modify the response before it reaches the client?"

Why This Is Asked

Middleware is the backbone of the request pipeline in modern web frameworks. Interviewers want to see if you can extend the pipeline for cross-cutting concerns like logging, authentication, or custom error handling.

Key Concepts

  • Middleware are components that form a pipeline to handle requests and responses.
  • Each component can choose to pass the request to the next component in the pipe.
  • Typical use cases: Logging, Exception Handling, Request Localization, Response Caching.
  • Order matters: The sequence in which middleware is added determines the execution order.

Question Variations

  • “What is middleware, and how does the request pipeline work in frameworks like Express or ASP.NET Core?”
  • “Can you describe a scenario where the order of middleware components would cause a bug?”
  • “How would you implement a custom middleware for global error handling?”
  • “How can middleware be used to modify the response before it reaches the client?”

Answers by Technology

+ Add Variant

Expected Answer (.NET 10 / C# 14)

In ASP.NET Core, middleware is a class that resides in the request pipeline. To write a custom one, you typically need a class with a constructor that takes RequestDelegate and an InvokeAsync method.

Real-time use case: A “Request Timing” middleware that logs how long each API request takes to process.

Why It Matters

Middleware allows you to separate cross-cutting concerns from your business logic. Instead of putting logging or auth logic in every controller action, you put it in a single middleware that wraps the entire request.

Code Example

public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestTimingMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var watch = Stopwatch.StartNew();
        
        // Pass to the next middleware in the pipe
        await _next(context);
        
        watch.Stop();
        Console.WriteLine($"Request {context.Request.Path} took {watch.ElapsedMilliseconds}ms");
    }
}

// Registration in Program.cs
// app.UseMiddleware<RequestTimingMiddleware>();

Common Mistakes

  • Incorrect Order: Placing error-handling middleware after the middleware that might throw an exception.
  • Forgetting await _next(context): This will short-circuit the pipeline, and no further middleware (or your controllers) will be executed.

Follow-up Questions

  • Singleton vs Scoped Middleware? (Answer: Middleware is usually a Singleton. If you need Scoped services, you must inject them into the InvokeAsync method, not the constructor).
  • What is the difference between app.Use and app.Run? (Answer: app.Use passes to the next middleware; app.Run is a terminal middleware that ends the pipeline).

Expected Answer (Java 26 / Spring Boot)

In the Java Spring ecosystem, the equivalent of middleware is a Filter (Servlet API) or an Interceptor (Spring MVC).

  • Servlet Filter: Operates at the lowest level of the web request.
  • HandlerInterceptor: Higher level, has access to the Spring Handler and ModelAndView.

Real-time use case: A “Logging Filter” that logs every incoming request’s method and URI before it reaches the controller.

Why It Matters

Filters allow you to implement cross-cutting concerns (logging, security, CORS, compression) in a centralized way. This keeps your business logic (Controllers) clean and focused on their primary task.

Code Example

@Component
public class RequestLoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest) request;
        System.out.println("Request: " + req.getMethod() + " " + req.getRequestURI());
        
        // Pass to the next filter in the chain
        chain.doFilter(request, response);
    }
}

Common Mistakes

  • Order of execution: Using @Order or FilterRegistrationBean to ensure security filters run before logging filters.
  • Blocking the chain: Forgetting to call chain.doFilter(), which will stop the request from ever reaching your Controller.

Follow-up Questions

  • Filter vs Interceptor? (Answer: Filters are part of the Servlet container; Interceptors are part of the Spring Framework and have access to Spring-specific metadata).
  • OncePerRequestFilter: Why use it? (Answer: To ensure a filter is executed exactly once per request, even if there are internal forwards or includes).

Expected Answer (PHP 8.5 / Laravel)

In Laravel, middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application.

Real-time use case: An “Active Subscription” middleware that checks if a logged-in user has an active paid plan before allowing them to access certain routes.

Why It Matters

Middleware allows you to centralize logic that applies to many routes. Instead of checking for a subscription in 50 different controller methods, you write it once and apply it to a route group.

Code Example

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class EnsureUserIsSubscribed
{
    public function handle(Request $request, Closure $next)
    {
        if ($request->user() && ! $request->user()->isSubscribed()) {
            return redirect('billing');
        }

        return $next($request);
    }
}

Common Mistakes

  • Heavy Logic: Putting complex database queries in middleware that runs on every request can significantly slow down the entire app.
  • Forgetting return $next($request): This will break the request, and the user will see a blank page as the controller is never reached.

Follow-up Questions

  • Global vs Route Middleware? (Answer: Global middleware runs on every request; Route middleware is assigned only to specific routes or groups).
  • What is ‘Middleware Terminate’? (Answer: A method that runs after the response has been sent to the browser, useful for logging or cleanup).

Expected Answer (Express 5.1.0 / Node.js 18+)

Express middleware is a function in the request-response chain with access to req, res, and next. It can inspect or enrich the request, send a response to end the cycle, or call next() to continue. Mount cross-cutting policies such as request IDs, authentication, validation, or logging at the narrowest appropriate scope: application-wide, a router, or a specific route. Ordering is behavior: parsers must run before handlers that use req.body, authentication before protected routes, and error middleware last.

Every normal middleware must either complete the response or call next() exactly once. In Express 5, a rejected promise from an async middleware is forwarded to the error chain, but callback-based async failures must still call next(err). Error middleware is distinguished by its four-argument signature.

Why It Matters

The middleware stack is the control plane for an Express service. An ordering error can bypass security, leave requests hanging, or return an inconsistent response.

Code Example

import crypto from 'node:crypto';
import express, { NextFunction, Request, Response } from 'express';

declare global { namespace Express { interface Request { requestId: string } } }

const app = express();
app.use((req: Request, res: Response, next: NextFunction) => {
  req.requestId = crypto.randomUUID();
  res.set('X-Request-Id', req.requestId);
  return next();
});
app.get('/orders', (req: Request, res: Response) => res.json({ requestId: req.requestId }));
app.listen(3000);

Common Mistakes

  • Neither returning a response nor calling next(): The request remains open until a timeout.
  • Mounting authorization after protected routes: The handler may execute before the policy is evaluated.

Follow-up Questions

  • How is error middleware recognized? (Answer: It has the four-argument signature (err, req, res, next).)
  • When should middleware be router-level? (Answer: When only one route group needs the policy.)

Expected Answer (Koa 3.2.1 / Node.js 18+)

Koa middleware receives ctx and next. It may enrich ctx.state, set a response, or use await next() to delegate and then perform upstream work. Register global policies such as error handling first, parsers before handlers that need bodies, and authorization before protected routes. Unlike a callback-only pipeline, Koa middleware naturally wraps downstream work.

Why It Matters

Middleware order defines security, request parsing, and response behavior for the entire application.

Code Example

import crypto from 'node:crypto';
import Koa, { Context, Next } from 'koa';

const app = new Koa();
app.use(async (ctx: Context, next: Next) => {
  const requestId = crypto.randomUUID();
  ctx.set('X-Request-Id', requestId);
  await next();
});
app.use((ctx: Context) => { ctx.body = { ok: true }; });
app.listen(3000);

Common Mistakes

  • Skipping await next() unintentionally: Later middleware cannot run.
  • Registering authentication after a protected route: The route can execute without its guard.

Follow-up Questions

  • When does code after await next() run? (Answer: When downstream middleware completes.)
  • Where should global errors be registered? (Answer: First, so they wrap all downstream middleware.)