intermediate By Mathias Paulenko

Front Controller Pattern

Route all incoming requests through a single handler that dispatches to the appropriate page command, centralizing request processing and security.

Topics: design

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Overview

The Front Controller Pattern routes all incoming requests through a single entry point — a central handler that processes each request and dispatches it to the appropriate command or controller. Rather than having every page handle its own request parsing, authentication, and logging, the Front Controller centralizes these cross-cutting concerns.

This is the pattern behind virtually every modern web framework. Spring Boot’s DispatcherServlet, Django’s WSGI handler, and Express.js middleware chains all embody Front Controller principles. One handler receives every request, performs common processing, then delegates to a page-specific controller.

When to Use

Use the Front Controller Pattern when:

  • Multiple pages share common preprocessing (authentication, logging, encoding)
  • You want a clean URL routing system independent of file structure
  • Request handling needs to be consistent across the entire application
  • You need centralized error handling and security checks

When to Avoid

  • Very simple applications with only a few pages (overhead without benefit)
  • Static sites where each page can be served directly
  • When the front controller becomes a God object handling too many responsibilities

Solution

Python

from typing import Callable, Dict
from http.server import BaseHTTPRequestHandler, HTTPServer

class Request:
    def __init__(self, path: str, method: str, headers: dict):
        self.path = path
        self.method = method
        self.headers = headers

class Response:
    def __init__(self, body: str, status: int = 200):
        self.body = body
        self.status = status

class FrontController:
    def __init__(self):
        self._handlers: Dict[str, Callable] = {}

    def register(self, path: str, handler: Callable):
        self._handlers[path] = handler

    def dispatch(self, request: Request) -> Response:
        # Common preprocessing
        if not self._authenticate(request):
            return Response("Unauthorized", 401)

        handler = self._handlers.get(request.path)
        if handler:
            return handler(request)
        return Response("Not Found", 404)

    def _authenticate(self, request: Request) -> bool:
        return request.headers.get("Authorization") == "Bearer valid"


def home_handler(request: Request) -> Response:
    return Response("Welcome to the home page")

def user_handler(request: Request) -> Response:
    return Response("User profile page")


# Setup
controller = FrontController()
controller.register("/", home_handler)
controller.register("/user", user_handler)

# Usage
req = Request("/user", "GET", {"Authorization": "Bearer valid"})
resp = controller.dispatch(req)
print(resp.status, resp.body)

Java

import java.util.*;

public record Request(String path, String method, Map<String, String> headers) {}
public record Response(String body, int status) {
    public Response(String body) { this(body, 200); }
}

@FunctionalInterface
interface RequestHandler {
    Response handle(Request request);
}

class FrontController {
    private final Map<String, RequestHandler> handlers = new HashMap<>();

    public void register(String path, RequestHandler handler) {
        handlers.put(path, handler);
    }

    public Response dispatch(Request request) {
        if (!authenticate(request)) {
            return new Response("Unauthorized", 401);
        }
        RequestHandler handler = handlers.get(request.path());
        if (handler != null) {
            return handler.handle(request);
        }
        return new Response("Not Found", 404);
    }

    private boolean authenticate(Request request) {
        return "Bearer valid".equals(request.headers().get("Authorization"));
    }
}

// Handlers
class HomeHandler implements RequestHandler {
    public Response handle(Request request) {
        return new Response("Welcome to the home page");
    }
}

class UserHandler implements RequestHandler {
    public Response handle(Request request) {
        return new Response("User profile page");
    }
}

// Usage
FrontController controller = new FrontController();
controller.register("/", new HomeHandler());
controller.register("/user", new UserHandler());

Request req = new Request("/user", "GET", Map.of("Authorization", "Bearer valid"));
Response resp = controller.dispatch(req);
System.out.println(resp.status() + " " + resp.body());

JavaScript

class Request {
  constructor(path, method, headers) {
    this.path = path;
    this.method = method;
    this.headers = headers;
  }
}

class Response {
  constructor(body, status = 200) {
    this.body = body;
    this.status = status;
  }
}

class FrontController {
  constructor() {
    this.handlers = new Map();
  }

  register(path, handler) {
    this.handlers.set(path, handler);
  }

  dispatch(request) {
    if (!this.authenticate(request)) {
      return new Response('Unauthorized', 401);
    }
    const handler = this.handlers.get(request.path);
    if (handler) {
      return handler(request);
    }
    return new Response('Not Found', 404);
  }

  authenticate(request) {
    return request.headers.authorization === 'Bearer valid';
  }
}

// Handlers
const homeHandler = (req) => new Response('Welcome to the home page');
const userHandler = (req) => new Response('User profile page');

// Setup
const controller = new FrontController();
controller.register('/', homeHandler);
controller.register('/user', userHandler);

// Usage
const req = new Request('/user', 'GET', { authorization: 'Bearer valid' });
const resp = controller.dispatch(req);
console.log(resp.status, resp.body);

Explanation

The Front Controller centralizes:

  • Request parsing: URL decoding, parameter extraction, content negotiation
  • Preprocessing: Authentication, authorization, input validation
  • Routing: Mapping URLs to the correct page controller
  • Postprocessing: Logging, metrics, response formatting
  • Error handling: Converting exceptions into HTTP status codes

Variants

VariantMechanismUse Case
Servlet FilterChain of filters in Java web appsAuthentication, encoding, compression
MiddlewareExpress.js / Django middleware stackCross-cutting request processing
DispatcherSpring MVC DispatcherServletFull MVC framework with view resolution
Reverse ProxyNginx / Apache as front controllerLoad balancing, SSL termination, caching

What Works

  • Keep the controller lean. Delegate page logic to dedicated command objects or sub-controllers.
  • Use a routing table. Map URL patterns to handlers declaratively rather than with nested if-else.
  • Process common concerns first. Authentication, CSRF protection, and input sanitization belong here.
  • Support interceptors. Allow middleware to modify requests and responses without changing the core.
  • Return early for errors. Invalid requests should fail fast before reaching page logic.

Common Mistakes

  • Bloated Front Controller that knows too much about page logic. It should dispatch, not implement.
  • Tight coupling to a specific view technology. The controller should not generate HTML directly.
  • Ignoring HTTP semantics. Returning 200 for every response hides errors from clients.
  • Missing error boundaries. Unhandled exceptions leak stack traces to users.
  • Synchronous blocking. The front controller should not perform long-running operations inline.

Real-World Examples

Spring MVC

DispatcherServlet is the Front Controller. It receives all HTTP requests, resolves controllers via annotations, and delegates view rendering to ViewResolver.

Django

Django’s WSGI application acts as a Front Controller. URL routing (urls.py) maps incoming requests to views after middleware processing.

Express.js

Express apps use a central app object with middleware. app.use(auth) and app.get('/user', handler) build a Front Controller pipeline.

Troubleshooting

  • Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
  • Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
  • Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
  • Tests break when the design changes: favor stable contracts over internal structure.
  • Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.

Quick Reference

  • Main command: run the base solution from the article and verify the expected result.
  • Validation: confirm tests pass and key metrics did not degrade.
  • Rollback: if something fails, revert the change and consult the Troubleshooting section.

Further Reading

  • Official documentation: check the current reference for the framework or tool used.
  • Related guides: explore the pattern and design-pattern guides for deeper coverage.
  • Complementary patterns: review design patterns applicable to your technology stack.
  • Public postmortems: study real incidents from teams that faced similar production issues.

Production Notes

  • Deploy gradually using canary or blue-green to catch regressions early.
  • Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
  • Document the rollback in the runbook; test the procedure in staging at least once per quarter.
  • Review structured logs with correlation IDs to trace requests end-to-end during incidents.

Key Takeaways

  • Apply front controller pattern when you need a practical solution for your use case.
  • Monitor performance after implementation; measure latency, errors, and resource usage before and after.
  • Check the Troubleshooting section for common failures; most have documented root causes with fixes.
  • Keep dependencies updated and run tests in CI to prevent production regressions.

Common Production Pitfalls

  • Applying the pattern where no abstraction is needed, adding accidental complexity.
  • Letting the pattern leak into unrelated modules and blur ownership boundaries.
  • Over-engineering the first implementation instead of starting simple and measuring pain.
  • Skipping contract tests, so refactors silently break consumers.
  • Ignoring failure modes that the pattern does not cover.
  • Using the pattern as a default instead of choosing the right tool for the current scale.
  • Forgetting to document when to stop using the pattern and what replaces it.
  • Missing observability around the pattern’s performance and error propagation.

Frequently Asked Questions

What is the difference between Front Controller and Page Controller?
[Page Controller](/patterns/page-controller-pattern/) uses one controller per page. Front Controller uses a single entry point for the entire application.
Does every web framework use Front Controller?
Most modern frameworks do. PHP's original model (one file per page) is the alternative Page Controller approach.
Can a Front Controller handle WebSocket connections?
Yes. The entry point inspects the upgrade header and routes to a WebSocket handler or an HTTP handler accordingly.