- Author: Amin Boulouma, Software Engineer
- Github source code: https://github.com/aminblm/ai_systems_design_from_scratch
- Engineering Blog: https://aminblm.github.io/ai_systems_design_from_scratch/blog/
The Starlette Philosophy: Complexity through Simplicity
Starlette is not just a web framework; it is an architectural masterclass. When you analyze its codebase, you don’t find the bloated “God Objects” common in legacy frameworks. Instead, you find a collection of highly decoupled, single-responsibility modules that prioritize pure Python patterns over framework magic.
The “midnight deployment spike” is rarely a threat to systems built like Starlette, because its design exposes every dependency explicitly in constructors, making failures predictable and debugging trivial.
The Theory: The Art of the Minimalist Interface
Starlette avoids the “Framework Trap”—where the framework dictates how you must write your code. Instead, it uses standard Python primitives: MutableMapping, Callable, and Awaitable. It treats the application as a simple function pipeline.
Glossary for Beginners
- Decoupled: Parts of the code that can exist without knowing the others exist. (Like LEGO blocks; they don’t need to know what they are connected to).
- Bloat: Unnecessary code that makes a program heavy and slow.
- Primitive: The basic “building blocks” of a language (like
list,dict, orfunction). - Dependency Injection: Passing the tools a module needs into it (via the constructor) rather than letting the module “hide” those dependencies inside itself.
The Pattern: Constructor-Based Dependency Injection
Starlette excels by passing everything into constructors. This makes unit testing incredibly simple because you can inject a “mock” version of any dependency.
# Starlette-style injection
class App:
def __init__(self, routes, middleware=None):
self.routes = routes
self.middleware = middleware or []
# Using @property to expose state without mutation
@property
def router(self):
return self._router
The Core: The Router as an Orchestrator
Starlette’s Router is a pure orchestrator. It doesn’t do the work; it routes the work. By using a registry pattern, it keeps the business logic separated from the HTTP/WebSocket protocol details.
Why Starlette’s Design Wins
- No Abstract Classes: Abstract classes often create rigid hierarchies. Starlette prefers Duck Typing and
collections.abc, making the code incredibly flexible. - Explicit over Implicit: If a piece of data is needed, it is passed in the constructor. This is the definition of SOLID design.
- Test-Driven Architecture: Because Starlette modules are decoupled, the test suite is not just for verification—it’s the perfect blueprint for how to use the framework.
Quick Reference: The Starlette Pattern Language
| Pattern | Role in Starlette | Why it matters |
|---|---|---|
| Strategy | Routing logic | Swappable behaviors without changing code |
| Orchestrator | Router class | Centralized traffic control |
| Factory | Middleware creation | Dynamic setup of service layers |
| Singleton | Application state | Ensures one source of truth for config |
Developer Checklist
- Are your dependencies explicit (passed in constructor) or hidden (imported globally)?
- Can your module be tested in isolation by injecting a simple
MutableMapping? - Are you using standard
collections.abctypes instead of custom types where possible? - Is your business logic decoupled from the transport layer (HTTP/WebSocket)?
Takeaways
- Minimize Magic: If you find yourself writing complex magic to handle routing, you’re missing an opportunity to use a simpler
Callable. - Typing is Documentation: Starlette proves that proper type hints make docstrings redundant.
- Own Your Stack: By building their own sockets and low-level tools, Starlette eliminates reliance on fragile, legacy-heavy dependencies.