Request lifecycle — the shape they all share
Every framework does the same dance: parse → route → middleware → handler → serialize → respond. Learn the shape once, read any framework faster.
Parsing: raw TCP → HTTP/1.1 line parser or HTTP/2 frame decoder → Request object. In Python: WSGI environ (sync) or ASGI scope + receive (async). In Node: http.IncomingMessage. In Go: http.Request. Frameworks wrap these with helpers (query parsing, cookie parsing, body readers). Rarely something you customize.
Routing algorithms: (a) trie-based (Gin, Fastify, httprouter) — O(path-length) matching, great for thousands of routes. (b) regex list (Django URLConf, Rails routes) — O(n) but supports complex patterns. (c) decorator-based (FastAPI, Flask) — registered at import time, usually hash/tree under the hood. (d) file-system routing (Next.js, Remix, Nuxt) — directory structure IS the route table. Each has priority rules (static > dynamic > catch-all); learn your framework's specifics.
Middleware execution order: varies subtly by framework. Express: strictly linear, next() calls the following middleware. Django: process_request top-down, then view, then process_response bottom-up. Rails Rack: Russian-doll, each middleware wraps the next's .call(). FastAPI: Starlette middleware runs before dependency injection. Read your framework's docs on ordering once; pitfalls (CORS before auth, compression before response-logging) are common bugs.
Validation / serialization layers: in typed frameworks, request parsing includes validation. FastAPI + Pydantic: path/query/body parsed into typed objects before the handler runs; validation failure → 422 auto-response. DRF serializers / Django Forms: similar. tRPC / zod: TypeScript equivalent on Node. These layers often contain more business rules than the handler body — know where input validation happens in your stack.
Handler contract: receives a request object (+ parsed params, + dependencies), returns a response (or raises). Sync frameworks expect a return value; async expect a coroutine. Response helpers: jsonify, JSONResponse, render_to_response, c.json(), res.json(). Streaming responses and WebSockets are 'handlers that never return' — the framework upgrades to a long-lived connection.
Exception handling pipeline: handler raises → exception middleware / handler chain → default 500 → response serializer. Mapping domain errors to HTTP codes in ONE place keeps handlers clean: ValidationError → 400, NotFound → 404, Forbidden → 403. Django's handler404, FastAPI's exception_handlers={}, Express's 4-arg error middleware (function (err, req, res, next)), Rails's rescue_from — same pattern, different syntax.
The hidden phase: response post-processing: compression (gzip/brotli), CORS headers, security headers (HSTS, CSP), cache-control, cookies. Usually outer-most middleware. A forgotten security-header middleware is a real pentest finding in many shops — not a coding bug, an architecture gap.
Grounded on https://docs.djangoproject.com/en/stable/topics/http/middleware/
Next up
ORM & data layer patterns
Active Record, Data Mapper, Query Builder, raw SQL. The data access pattern your framework ships shapes how you think about persistence.