Routes and Path Operations
Table of Contents
Understanding Routes and Path Operations in FastAPI
In FastAPI, everything starts with routes, also called path operations. This is how you tell your application, “When a request comes to this URL with this method, run this function.”
This chapter focuses on how routing works in FastAPI, how to define endpoints, and how to make good, clear URLs.
Basic Path Operation: The First Route
In FastAPI, you usually create an app object and then define routes using decorators that match HTTP methods.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, world"}What happens here:
app = FastAPI()creates the main application.@app.get("/")registers a route that:- listens on the path
/ - responds to the HTTP method
GET read_rootis the function that will run for that route.
FastAPI calls this a path operation function.
If you run this app and open http://127.0.0.1:8000/ in your browser, you will get:
{"message": "Hello, world"}
Important rule:
A path operation = HTTP method + path + Python function.
HTTP Methods as Path Operation Decorators
FastAPI provides decorators that directly correspond to HTTP methods:
@app.get(path)@app.post(path)@app.put(path)@app.patch(path)@app.delete(path)- (and also
@app.options,@app.head, etc., when needed)
Example: a few different methods on different paths.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items")
def list_items():
return [{"id": 1, "name": "Book"}, {"id": 2, "name": "Phone"}]
@app.post("/items")
def create_item():
return {"message": "Item created"}
@app.get("/status")
def get_status():
return {"status": "ok"}
If you try to POST to /status, FastAPI will return a 405 Method Not Allowed, because only GET is defined there.
You can technically use the same path with two methods:
@app.get("/profile")
def get_profile():
return {"mode": "view"}
@app.post("/profile")
def update_profile():
return {"mode": "update"}The difference is in the HTTP method, not in the function name.
Path Formats and Best Practices
Paths are the URL parts after the domain, for example:
https://example.com/items→ path is/itemshttps://example.com/api/v1/users/123→ path is/api/v1/users/123
Some practices for readable and REST friendly paths:
| Good pattern | Why |
|---|---|
/users | Plural noun for a collection |
/users/123 | Single resource, identified by its ID |
/users/123/orders | Nested resource that belongs to another resource |
/health or /status | Simple health or status check |
Avoid putting verbs in paths like /getUser or /createUser, since the HTTP method already describes the action.
Example of more realistic routes:
@app.get("/users")
def list_users():
...
@app.post("/users")
def create_user():
...
@app.get("/users/{user_id}")
def get_user(user_id: int):
...Path Operation Function Return Types
A path operation function can return:
- Dictionaries or lists, which FastAPI will convert to JSON.
- Pydantic models, covered in a later chapter.
- Plain strings.
Responseobjects for advanced control.
Some examples:
@app.get("/text")
def read_text():
return "Simple text response"
@app.get("/dict")
def read_dict():
return {"key": "value", "number": 42}
@app.get("/list")
def read_list():
return [1, 2, 3, 4]
@app.get("/status")
def read_status():
return {"status": "ok", "online": True}
FastAPI automatically sets the Content-Type: application/json header when you return dicts or lists.
Operation IDs and Function Names
Function names are not part of the URL or HTTP protocol, but they matter for:
- Code readability.
- Auto generated documentation and client code, where the function name becomes an operation ID.
Bad naming:
@app.get("/items")
def myfunc1():
...Better naming:
@app.get("/items")
def list_items():
...
Important rule:
Use clear, descriptive function names like get_user, create_item, delete_order.
This makes your code and docs easier to understand.
Using Path Parameters in Routes
Often, you need part of the URL to be dynamic, for example /users/123. FastAPI supports this with path parameters.
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}Explanation:
{item_id}in the path means this part is variable.- The function parameter
item_id: intmatches the path parameter name. - FastAPI converts the URL string to
int. If conversion fails, FastAPI returns a validation error.
Some variations:
@app.get("/users/{username}")
def read_user(username: str):
return {"username": username}
@app.get("/orders/{year}/{month}")
def read_orders(year: int, month: int):
return {"year": year, "month": month}If you call:
/users/alice→username = "alice"/orders/2024/5→year = 2024,month = 5
Path parameter names must match exactly:
@app.get("/items/{item_id}")
def read_item(id: int): # Wrong: name does not match
...This will not work correctly. It must be:
@app.get("/items/{item_id}")
def read_item(item_id: int): # Correct
...Path Order and Conflicts
FastAPI decides which route to use based on both:
- The path pattern.
- The HTTP method.
You can define static paths and dynamic paths side by side:
@app.get("/users/me")
def read_current_user():
return {"user": "current"}
@app.get("/users/{user_id}")
def read_user(user_id: int):
return {"user_id": user_id}
If you visit /users/me, FastAPI will match the first route, even though the second looks like it could match any string. That is because:
- Static paths are matched before dynamic ones.
Watch out for conflicts like this:
@app.get("/files/{file_path}")
def read_file(file_path: str):
...
@app.get("/files/static")
def read_static():
...Depending on the order and the exact patterns, you may get unexpected matches. Prefer using clearly distinct paths when possible.
Using Path Converters: `path` Type
Sometimes you want a path parameter to capture everything including slashes, for example /files/images/2024/08/pic.png.
By default, FastAPI will stop at the first slash inside a {param}. To include slashes, use the path converter:
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}Example URLs and values:
| URL | file_path value |
|---|---|
/files/readme.txt | "readme.txt" |
/files/images/logo.png | "images/logo.png" |
/files/2024/08/report/final.pdf | "2024/08/report/final.pdf" |
Extra Configuration on Path Operations
The decorators accept extra keyword arguments to describe your path operation. These will show up in the automatic docs.
Common options:
summary: short description.description: longer description.tags: list of tag strings to group endpoints.response_description: description of the response.status_code: default response status code.
Example:
from typing import List
@app.get(
"/items",
summary="List all items",
description="Returns a list of all items in the catalog.",
tags=["items"],
response_description="A list of item objects",
)
def list_items() -> List[dict]:
return [
{"id": 1, "name": "Book"},
{"id": 2, "name": "Laptop"},
]These details are not required, but they improve the generated docs and are very useful for larger APIs.
Grouping Routes with Tags
tags help you group endpoints in the docs. Imagine you have users and items:
@app.get("/users", tags=["users"])
def list_users():
...
@app.post("/users", tags=["users"])
def create_user():
...
@app.get("/items", tags=["items"])
def list_items():
...
@app.post("/items", tags=["items"])
def create_item():
...In the auto generated docs (Swagger UI), users and items endpoints will appear in separate sections.
You can also define multiple tags for one endpoint:
@app.get("/users/{user_id}/orders", tags=["users", "orders"])
def user_orders(user_id: int):
...Using `APIRouter` to Organize Routes
As your application grows, a single app.py can become crowded. FastAPI provides APIRouter to structure routes into modules.
Defining a Router
users.py:
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
def list_users():
return [{"id": 1, "name": "Alice"}]
@router.get("/{user_id}")
def get_user(user_id: int):
return {"id": user_id, "name": "User " + str(user_id)}Key points:
APIRouter(prefix="/users")means every route inside will start with/users.@router.get("/")becomes/users/.@router.get("/{user_id}")becomes/users/{user_id}.tags=["users"]applies the tag to all routes in this router.
Including Routers in the Main App
main.py:
from fastapi import FastAPI
from .users import router as users_router
app = FastAPI()
app.include_router(users_router)Now your app has:
GET /users/GET /users/{user_id}
You can include multiple routers:
from .items import router as items_router
app.include_router(users_router)
app.include_router(items_router)You can also add an extra prefix while including:
app.include_router(users_router, prefix="/api")Now the user routes will be:
/api/users//api/users/{user_id}
This is useful when you version your API, for example:
app.include_router(users_v1_router, prefix="/api/v1")
app.include_router(users_v2_router, prefix="/api/v2")Combining Path Parameters with Query Parameters and Body
Path operations often receive data from multiple places:
- Path: resource identifier.
- Query parameters: filters, pagination, options.
- Body: JSON or form data for create/update operations.
Example route combining all three:
from typing import Optional
from fastapi import Body
@app.put("/items/{item_id}")
def update_item(
item_id: int,
q: Optional[str] = None,
in_stock: bool = True,
description: str = Body(...),
):
return {
"item_id": item_id,
"query": q,
"in_stock": in_stock,
"description": description,
}Behavior:
/items/5path →item_id = 5/items/5?q=phone&in_stock=falsequery string →q = "phone",in_stock = False- Request body JSON like
{"description": "Updated item"}→description = "Updated item"
How to separate these concerns properly is covered more in the dedicated chapters about query parameters and request bodies, but it is helpful to see them combined in a single route.
Returning Different Status Codes
Sometimes you want a path operation to have a default status code that is not 200.
You can set status_code in the decorator:
from fastapi import status
@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item():
return {"message": "Created"}Here, the response will use status code 201 by default.
You can also return other codes conditionally using HTTPException in the dedicated exception handling chapter, but the basic idea is:
from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id == 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id}Simple Practical Examples
Here is a mini API that shows several path operations clearly:
from typing import List, Optional
from fastapi import FastAPI
app = FastAPI()
fake_items = [
{"id": 1, "name": "Book"},
{"id": 2, "name": "Phone"},
]
@app.get("/items", tags=["items"])
def list_items(skip: int = 0, limit: int = 10) -> List[dict]:
return fake_items[skip : skip + limit]
@app.get("/items/{item_id}", tags=["items"])
def get_item(item_id: int):
for item in fake_items:
if item["id"] == item_id:
return item
return {"message": "Item not found"}
@app.post("/items", tags=["items"])
def create_item(name: str):
new_id = len(fake_items) + 1
item = {"id": new_id, "name": name}
fake_items.append(item)
return item
@app.delete("/items/{item_id}", tags=["items"])
def delete_item(item_id: int):
global fake_items
fake_items = [item for item in fake_items if item["id"] != item_id]
return {"message": "Item deleted"}Notes:
- The
skipandlimithere are query parameters, not path parameters. - The path parameters are
{item_id}inget_itemanddelete_item. - The routes form a basic CRUD for items.
Summary
In FastAPI:
- A route or path operation connects an HTTP method and a path to a Python function.
- You use decorators like
@app.get("/path")or@app.post("/path"). - Dynamic parts of paths use path parameters, for example
/items/{item_id}. - Path parameters must match function parameter names and can be typed.
- Routes can be grouped and organized with
APIRouterandtags. - The decorator accepts extra options like
summary,description,tags, andstatus_code. - Careful path design and clear function names make your API easy to understand and maintain.
In the following chapters, you will see how to work with path parameters, query parameters, and request bodies in more detail, and how to build more complex and useful path operations.
Views: 8
KAHIBARO