Routing Compared: Django, Express, Rails, Laravel & FastAPI
Web Framework Internals
Chapter 3 ยท Routing Compared: Django, Express, Rails, Laravel & FastAPI
Chapter 2 built one router by hand, covering the mechanics every real framework has to solve underneath. This chapter takes the exact same real-world route โ "get one user by their ID" โ and shows it expressed in five real frameworks' own syntax, then digs into three genuinely different real features that go beyond what Chapter 2's own router does at all.
The Same Route, Five Real Syntaxes
Django, Express, Laravel, and FastAPI all share the same real underlying philosophy: explicit registration, one line (or decorator) per route, matching Chapter 2's own hand-built router closely. Rails does something genuinely different.
Rails' Real Convention-Driven Routing
resources :users is a single line, but it isn't registering one route — it's generating a full, real, documented set of seven RESTful routes at once, following Rails' own naming convention rather than requiring each one written out by hand:
| HTTP Method + Path | Purpose | Controller Action |
|---|---|---|
| GET /users | List all users | index |
| GET /users/new | Form for a new user | new |
| POST /users | Create a user | create |
| GET /users/:id | Show one user | show |
| GET /users/:id/edit | Form to edit a user | edit |
| PATCH/PUT /users/:id | Update a user | update |
| DELETE /users/:id | Delete a user | destroy |
GET /users/new and GET /users/:id are exactly the general-vs-specific conflict Chapter 2's own registration-order section built a real bug around — and Rails' resources helper generates both routes internally in the correct order every single time, since it's following one documented, fixed convention rather than depending on whichever order a developer happened to write two separate lines in. The tradeoff is genuine, not one-sided: a developer reading resources :users alone has to already know the convention to know what routes actually exist, where Chapter 2's own explicit router (and Django, Express, Laravel, and FastAPI's own real routing files) make every registered route visible directly in the source.
Laravel's Real Route Model Binding
The function (User $user) signature in Laravel's own example above isn't a coincidence of syntax — it's a real, documented feature. Laravel's own official documentation states it directly: "Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name." Type-hinting the parameter as User, with a variable name ($user) matching the route segment ({user}), is enough — Laravel queries the database and hands the controller a real, already-loaded model instance, not a raw string or integer.
<int:id>) validate that a segment looks like a valid ID — digits only — but they have no concept of a real database at all, so a well-formed but nonexistent ID (/users/999999, where no such row exists) would still reach the handler successfully. Route model binding goes one real step further: it doesn't just validate the shape of the value, it resolves the value against real data, and only reaches the handler at all if a matching row genuinely exists.
FastAPI's Real Type-Hint-Driven Validation & Auto-Generated Docs
FastAPI's own item_id: int annotation does real double duty, confirmed directly by FastAPI's own documentation: "With that type declaration, FastAPI gives you automatic request 'parsing'" and, from the identical declaration, "FastAPI gives you data validation." A request to /users/3 hands the handler a genuine Python int. A request to /users/foo is rejected before the handler ever runs, with a real, structured error response:
int annotation is also what FastAPI reads to build its own automatically generated, interactive documentation at /docs — the Swagger UI page directly shows item_id as a required integer parameter, generated from the identical source-code annotation that Chapter 2's own typed converters would have needed a separate, hand-written regex to express. FastAPI genuinely takes Chapter 2's own "typed converter" idea and pushes it one real step further: instead of a regex string chosen from a fixed converter table, the type hint is the converter declaration — and it's reused for validation, parsing, and documentation all at once, under the hood via Pydantic.
Express's Real, Deliberately Minimal Style
Consistent with Chapter 1's own finding that Express ships routing and middleware robustly while leaving other capabilities pluggable, Express's own routing has no equivalent to any of the three features above. req.params.id in the example at the top of this chapter is always a plain string, whatever characters actually appeared in the URL — there's no built-in typed-converter table, no automatic model resolution, and no automatically generated documentation. Validating, converting, or looking up that value against a real database is left entirely to whatever the developer writes next, by hand, inside the handler itself — matching precisely the "leaves it pluggable" position Chapter 1 already established Express takes toward data access and templating too.
One Feature, Five Real Answers
| Feature | Django | Express | Rails | Laravel | FastAPI |
|---|---|---|---|---|---|
| Registration style | Explicit | Explicit | Convention (resources) | Explicit | Explicit (decorator) |
| Typed path segments | Yes — <int:id> etc. | No — always a string | Yes, via constraints | Yes, plus real model binding | Yes — the Python type hint itself |
| Auto-resolves to a real DB row | No (a view queries manually) | No | No (a controller queries manually) | Yes — real route model binding | No (a dependency/handler queries manually) |
| Auto-generated interactive docs | No | No | No | No | Yes — a real /docs Swagger UI |
Where This Course Is Headed
Templating and view rendering — how it actually works (Chapter 4), then compared across Django Templates/Jinja2, ERB, Blade, EJS/Pug, and JSX (Chapter 5).
Hands-On Exercises
Write the full Rails routes.rb line and the equivalent Django urls.py line needed to support all 7 RESTful actions for a "posts" resource (not just "show"), and list which real HTTP method + path pairs each one produces.
๐ View solutionExplain the real difference between Chapter 2's own typed <int:id> converter and Laravel's real route model binding, using a concrete example of a well-formed but nonexistent ID (e.g. /users/999999) and what each one actually does with it.
๐ View solutionExplain why FastAPI's approach to typed path parameters is described as "the same type hint doing double duty," identifying the two genuinely separate real outputs (beyond just the runtime-converted value) that a single int annotation produces.
๐ View solutionChapter 3 Quick Reference
- Explicit vs. convention โ Django/Express/Laravel/FastAPI register each route by hand; Rails' resources generates a full real 7-route RESTful set from one line
- Rails resolves Chapter 2's own gotcha by convention โ /users/new vs. /users/:id is always generated in the correct order
- Laravel's route model binding โ a real, verified feature: a type-hinted variable resolves to a real Eloquent row, with an automatic 404 if none exists
- FastAPI's type hints do double duty โ the same int annotation drives both request validation and the real, auto-generated /docs documentation
- Express stays deliberately minimal โ no typed converters, no model resolution, no auto docs; every path segment is a plain string, left entirely to the developer
- Next chapter: Templating & view rendering โ how it actually works