Routing and Endpoints
Learn how to create and manage routes and endpoints effectively in FastAPI applications.
Content
Defining Routes
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Defining Routes in FastAPI — The No-Chill Breakdown
You already learned how to run a FastAPI server and made your very first app. Now let’s teach your app how to answer the door when requests knock.
You're not hearing this intro because you already know: we have an app instance (app = FastAPI()), we can run it with uvicorn, and your file isn't haunted by import errors. This lesson builds on that: how to define routes (a.k.a. path operations, a.k.a. endpoints), organize them, and avoid the chaos of spaghetti routing.
Why routes matter (and why they should be sexy)
A route is the mapping between a URL + HTTP method and the Python code that runs when someone hits that URL. Think of FastAPI as a restaurant: the route is the waiter who takes a particular order and sends it to the right chef. If your routing is messy, customers get soup when they ordered tacos. Nobody wins.
Key things we'll cover:
- How to declare routes with decorators like
@app.get(...)and@app.post(...) - Path vs. query parameters vs. request body
- Type hints, validation, and automatic docs behavior
- Organization with
APIRouterandinclude_router
Basic Route Syntax (quick reminder + example)
You already used @app.get("/") in the First FastAPI Application. Now expand that toolbox.
Code:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello from your root route"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
class Item(BaseModel):
name: str
price: float
@app.post("/items/", status_code=201)
async def create_item(item: Item):
return {"created": item}
Quick notes:
- Decorators are path operations:
@app.get,@app.post,@app.put,@app.delete, etc. - Path parameters go in the decorator string:
/items/{item_id}. FastAPI uses type hints to validate and convert them (hereitem_id: int). - Query parameters are function params with default values (or annotated as Optional).
q: str | None = Nonebecomes a query param. - Request bodies use Pydantic models (e.g.,
Item) automatically parsed from JSON.
Path vs Query vs Body — a tiny cheat-sheet table
| Where it lives | How you declare it | Use for | Example |
|---|---|---|---|
| Path param | In path string + function arg | Required identifier, used for routing | /users/{id} -> id: int |
| Query param | Function arg with default | Optional filters, search strings | ?q=fastapi |
| Body | Pydantic model param | Complex objects sent as JSON | POST /items with { "name": "x" } |
Important routing rules (so your app doesn’t behave like a gremlin)
- Order of specificity matters.
/items/newshould be declared before/items/{item_id}if both exist, otherwise/items/newwill match{item_id}and you’ll cry. - Types in paths are enforced. If you declare
{id:int},/id/as-stringwill 404 instead of route — FastAPI validates and rejects. - Trailing slashes:
/pathand/path/are distinct unless you configure otherwise. Choose a convention and be consistent. - Async vs sync handlers: Use
async deffor I/O heavy endpoints. FastAPI runs sync functions in a threadpool automatically, butasync defis nicer for async DB/client libs.
Advanced: Organizing routes with APIRouter
Large apps need structure. APIRouter = the modular routing system.
Example:
from fastapi import APIRouter
items_router = APIRouter(prefix="/items", tags=["items"])
@items_router.get("/")
async def list_items():
return ["apple", "banana"]
@items_router.get("/{item_id}")
async def get_item(item_id: int):
return {"id": item_id}
# in main app file
app.include_router(items_router)
Why this rocks:
- You can group endpoints by business area (items, users, auth).
- You can add
prefix,tags,dependencies, and response models per router. - It makes tests and imports less chaotic.
Common gotchas and how to avoid them
- Conflicting routes: If you have
/users/meand/users/{user_id}, declare/users/mefirst. - Overloading methods: If you declare two
@app.get("/x")functions, the last one wins — don’t do it. - Validation surprises: If a Pydantic model requires a field, missing it becomes a 422 Unprocessable Entity. Good — FastAPI is protecting you from sad data.
- Return types and docs: Use
response_modelto shape the OpenAPI docs and the returned data. It will also filter out secrets.
Example using response_model:
from pydantic import BaseModel
class PublicItem(BaseModel):
name: str
@app.get("/public-items/{id}", response_model=PublicItem)
async def public_item(id: int):
db_item = {"name": "secret cookie", "secret": 42}
return db_item # response_model will exclude 'secret'
Tiny patterns that make life easier
- Use path types for clean URLs:
/users/{user_id:int}rather than/users?id=123for resource identity. - Validate with Pydantic for request bodies — no manual parsing.
- Keep handlers small: one function = one responsibility.
- Use APIRouter to group routes and include them in one place.
Questions to make you smarter (answer them in your head, or in code)
- What happens if you swap the order of
/items/newand/items/{item_id}? (Try it.) - When would you prefer a query param over a path param? Think: optional filters vs resource identity.
- How does
response_modelhelp when your database returns sensitive fields?
Expert take:
"A clear routing structure is 80% of keeping an API maintainable. The rest is tests and not naming things 'misc' or 'helper_1'."
Closing — TL;DR + Next moves
- Define routes with decorators like
@app.get,@app.post, using path strings and typed function params. - Use type hints — FastAPI uses them for validation, conversion, and docs.
- Organize with
APIRouterandinclude_routerso your project doesn’t become a single 1000-line file of sorrow.
Final thought: building great routing is like arranging furniture — it looks simple until you trip over the ottoman at 2 AM. Plan your layout, name things meaningfully, and keep endpoints focused. Next up, we'll wire these routes to real databases and authentication so your API does things that matter in the world (and not just return cute JSON objects).
Version: Defining Routes — No-Chill Breakdown
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!