Routing & Parameters
Route decorators vs app.get()
Section titled “Route decorators vs app.get()”In Express you call app.get(path, handler). In FastAPI you decorate a function with @app.get(path). The decorator registers the function as the handler for that HTTP method + path combination. The function can be async def or a plain def.
// Expressconst app = express();
app.get('/items', (req, res) => { res.json([{ id: 1, name: 'Widget' }]);});
app.post('/items', (req, res) => { const body = req.body; // manually parsed res.status(201).json(body);});# FastAPIfrom fastapi import FastAPI
app = FastAPI()
@app.get("/items")async def list_items(): return [{"id": 1, "name": "Widget"}]
@app.post("/items", status_code=201)async def create_item(item: dict): return itemFastAPI supports all standard HTTP methods: @app.get, @app.post, @app.put, @app.patch, @app.delete, @app.options.
Path parameters
Section titled “Path parameters”Wrap the parameter name in curly braces in the path string, then add a matching typed argument to the function. FastAPI casts the value and returns a 422 Unprocessable Entity automatically if the cast fails.
// Express — everything is a string, cast manuallyapp.get('/items/:id', (req, res) => { const id = parseInt(req.params.id, 10); if (isNaN(id)) return res.status(422).json({ error: 'id must be integer' }); res.json({ id, name: 'Widget' });});
// NestJS@Get(':id')findOne(@Param('id', ParseIntPipe) id: number) { return { id, name: 'Widget' };}# FastAPI — type hint does the cast + validationfrom fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")async def get_item(item_id: int): # GET /items/abc → 422 automatically return {"id": item_id, "name": "Widget"}
# Multiple path params@app.get("/users/{user_id}/items/{item_id}")async def get_user_item(user_id: int, item_id: int): return {"user_id": user_id, "item_id": item_id}Query parameters
Section titled “Query parameters”Any function argument that is NOT in the path string is treated as a query parameter. Add a default value to make it optional.
// Expressapp.get('/items', (req, res) => { const skip = Number(req.query.skip) || 0; const limit = Number(req.query.limit) || 10; const q = req.query.q as string | undefined; res.json({ skip, limit, q });});# FastAPI — query params from function signaturefrom fastapi import FastAPIfrom typing import Optional
app = FastAPI()
@app.get("/items")async def search_items( skip: int = 0, # GET /items?skip=5 limit: int = 10, # GET /items?limit=20 q: Optional[str] = None, # GET /items?q=widget): return {"skip": skip, "limit": limit, "q": q}Try it — routing logic as a plain dict
Section titled “Try it — routing logic as a plain dict”The in-browser runner cannot run a real HTTP server, but you can explore how FastAPI-style route matching works using a plain dict:
# Route registry — mirrors how FastAPI maps handlersroutes = { ("GET", "/items"): "list_items", ("POST", "/items"): "create_item", ("GET", "/items/{id}"): "get_item", ("PUT", "/items/{id}"): "update_item", ("DELETE", "/items/{id}"): "delete_item",}
def match_route(method: str, path: str) -> str: # exact match first key = (method, path) if key in routes: return routes[key] # template match (naive) for (m, tmpl), handler in routes.items(): if m != method: continue parts_tmpl = tmpl.split("/") parts_path = path.split("/") if len(parts_tmpl) != len(parts_path): continue if all(t.startswith("{") or t == p for t, p in zip(parts_tmpl, parts_path)): return handler return "404 Not Found"
tests = [ ("GET", "/items"), ("POST", "/items"), ("GET", "/items/42"), ("DELETE", "/items/99"), ("GET", "/unknown"),]for method, path in tests: print(f"{method:6s} {path:20s} -> {match_route(method, path)}")Loading Python runtime (first run only)…