1 The Problem
We want to build a web framework — the thing Flask and Express are. You register routes with a decorator (@app.route("/hello")), and the framework matches incoming URLs to the right function. It teaches how frameworks actually work by building one: decorators that register, and a dispatcher that routes.
2 How to Think About It
Think about what @app.route really does, before any code:
@route decorator simply adds an entry to that map: this URL runs this function. → 3. When a request comes in, look up its URL in the map and call the matching function. → That dictionary lookup is routing. The decorator is just a nice way to fill the dictionary.
3 The Build — explained part by part
Here is a complete mini web framework. Each part is explained below.
class App:
def __init__(self):
self.routes = {} # path -> handler function
def route(self, path):
# A decorator that registers a handler for a path.
def decorator(handler):
self.routes[path] = handler
return handler
return decorator
def handle(self, path):
# Dispatch a request to its handler.
handler = self.routes.get(path)
if handler is None:
return (404, "Not Found")
return (200, handler())
app = App()
@app.route("/")
def home():
return "Welcome home!"
@app.route("/about")
def about():
return "About us"
if __name__ == "__main__":
print(app.handle("/")) # (200, 'Welcome home!')
print(app.handle("/about")) # (200, 'About us')
print(app.handle("/missing")) # (404, 'Not Found')
def route(self, path): — this returns a decorator. When you write
@app.route("/about"), Python calls route("/about"), gets back decorator, and applies it to your function.def decorator(handler): self.routes[path] = handler — the actual registration: store this function under this path, then return it unchanged. That one line is what
@app.route does — no magic.handle(self, path) — the dispatcher: look up the path; if found, call the handler and return
200 with its result; if not, return 404. This is routing in its purest form.@app.route("/") — using our framework now looks exactly like using Flask — because this is how Flask works underneath.
@app.route(p) is exactly func = app.route(p)(func).return handler from the inner decorator..get(path) and return 404 when None.4 Test & Prove Each Part
We test that routes register and dispatch correctly, including the not-found case.
class App:
def __init__(self):
self.routes = {}
def route(self, path):
def decorator(handler):
self.routes[path] = handler
return handler
return decorator
def handle(self, path):
handler = self.routes.get(path)
if handler is None:
return (404, "Not Found")
return (200, handler())
def test_registered_route():
"""A registered route returns 200 and its body."""
app = App()
@app.route("/hi")
def hi():
return "hello"
assert app.handle("/hi") == (200, "hello")
def test_unknown_route():
"""An unknown route returns 404."""
app = App()
assert app.handle("/nope") == (404, "Not Found")
def test_multiple_routes():
"""Each route maps to its own handler."""
app = App()
@app.route("/a")
def a():
return "A"
@app.route("/b")
def b():
return "B"
assert app.handle("/a") == (200, "A")
assert app.handle("/b") == (200, "B")
Run with pytest -v. These tests use the framework exactly as a developer would — registering routes with the decorator and checking dispatch. That the decorator and dispatcher are simple enough to test in a few lines is the whole lesson.
5 API Documentation
The framework’s API — how a developer uses it.
Usage
@app.route("/hello")
def hello():
return "Hi!"Returns
(200, "Hi!") or (404, "Not Found")6 Run It & Automate It
python3 framework.pySee routes dispatch. Then connect it to the Simple Web Server project to handle real HTTP requests!
Jenkins runs the tests automatically — each line explained below.
(200, 'Welcome home!')
(200, 'About us')
(404, 'Not Found')None.return handler at the end.KeyError on dispatch.self.routes.get(path) not self.routes[path], so missing routes return None.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- URL parameters. Support
/user/<id>dynamic routes. (Teaches: pattern matching.) - HTTP methods. Register separate GET and POST handlers. (Teaches: method routing.)
- Wire to a real server. Connect to http.server for actual requests. (Teaches: integration.)
@app.route is a decorator that fills it. Understanding this makes Flask, Django, and Express feel obvious rather than mysterious. Related: Decorators, http.server.