python 43 lines · 8 steps

URL normalization middleware in FastAPI

A Starlette middleware that rewrites legacy and messy paths, then 308-redirects clients to the clean canonical URL.

Explained by highlit
1import re
2from starlette.datastructures import URL
3from starlette.middleware.base import BaseHTTPMiddleware
4from starlette.responses import RedirectResponse
5from starlette.types import ASGIApp
6 
7 
8LEGACY_PREFIXES = {
9 "/api/v0/": "/api/v1/",
10 "/users/profile": "/api/v1/users/me",
11}
12 
13_MULTI_SLASH = re.compile(r"/{2,}")
14 
15 
16class URLNormalizationMiddleware(BaseHTTPMiddleware):
17 def __init__(self, app: ASGIApp, *, redirect_slashes: bool = True) -> None:
18 super().__init__(app)
19 self.redirect_slashes = redirect_slashes
20 
21 async def dispatch(self, request, call_next):
22 original = request.url.path
23 normalized = self._normalize(original)
24 
25 if normalized != original:
26 target = request.url.replace(path=normalized)
27 return RedirectResponse(str(target), status_code=308)
28 
29 return await call_next(request)
30 
31 def _normalize(self, path: str) -> str:
32 for legacy, current in LEGACY_PREFIXES.items():
33 if path == legacy or path.startswith(legacy):
34 path = current + path[len(legacy):]
35 break
36 
37 path = _MULTI_SLASH.sub("/", path)
38 path = path.lower()
39 
40 if self.redirect_slashes and len(path) > 1 and path.endswith("/"):
41 path = path.rstrip("/")
42 
43 return path
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Redirecting to a canonical URL keeps one authoritative path per resource instead of scattering logic across handlers.
  2. 2Use a 308 redirect so clients preserve the original HTTP method and body when following the new location.
  3. 3Centralizing path rewrites in middleware means every route benefits without touching individual endpoints.

Related explainers

Share this explainer

Here's the card — post it anywhere.

URL normalization middleware in FastAPI — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code