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 (urls.py)
from django.urls import path from . import views urlpatterns = [ path('users/<int:id>/', views.user_detail), ]
Express (routes/users.js)
app.get('/users/:id', (req, res) => { const id = req.params.id; // always a plain string res.json({ id }); });
Rails (config/routes.rb)
resources :users, only: [:show] # generates: GET /users/:id -> UsersController#show
Laravel (routes/web.php)
Route::get('/users/{user}', function (User $user) { return $user->email; });
FastAPI (main.py)
@app.get("/users/{item_id}") async def read_user(item_id: int): return {"item_id": item_id}

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 + PathPurposeController Action
GET /usersList all usersindex
GET /users/newForm for a new usernew
POST /usersCreate a usercreate
GET /users/:idShow one usershow
GET /users/:id/editForm to edit a useredit
PATCH/PUT /users/:idUpdate a userupdate
DELETE /users/:idDelete a userdestroy
Chapter 2's own registration order gotcha is resolved automatically here, by convention
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.

A real, automatic 404 — not a value the developer has to check
Per Laravel's own documentation: "If a matching model instance is not found in the database, a 404 HTTP response will automatically be generated." Chapter 2's own typed converters (<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:

{ "detail": [ { "type": "int_parsing", "loc": ["path", "item_id"], "msg": "Input should be a valid integer, unable to parse string as an integer", "input": "foo" } ] }
The same type hint that validates also documents
That exact 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

FeatureDjangoExpressRailsLaravelFastAPI
Registration styleExplicitExplicitConvention (resources)ExplicitExplicit (decorator)
Typed path segmentsYes — <int:id> etc.No — always a stringYes, via constraintsYes, plus real model bindingYes — the Python type hint itself
Auto-resolves to a real DB rowNo (a view queries manually)NoNo (a controller queries manually)Yes — real route model bindingNo (a dependency/handler queries manually)
Auto-generated interactive docsNoNoNoNoYes — 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

Exercise 1

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 solution
Exercise 2

Explain 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 solution
Exercise 3

Explain 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 solution

Chapter 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