KAHIBARO
Discord Login Register

Routes and Path Operations

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.

python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
    return {"message": "Hello, world"}

What happens here:

If you run this app and open http://127.0.0.1:8000/ in your browser, you will get:

json
{"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:

Example: a few different methods on different paths.

python
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:

python
@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:

Some practices for readable and REST friendly paths:

Good patternWhy
/usersPlural noun for a collection
/users/123Single resource, identified by its ID
/users/123/ordersNested resource that belongs to another resource
/health or /statusSimple 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:

python
@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:

Some examples:

python
@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:

Bad naming:

python
@app.get("/items")
def myfunc1():
    ...

Better naming:

python
@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.

python
@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}

Explanation:

Some variations:

python
@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:

Path parameter names must match exactly:

python
@app.get("/items/{item_id}")
def read_item(id: int):   # Wrong: name does not match
    ...

This will not work correctly. It must be:

python
@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:

You can define static paths and dynamic paths side by side:

python
@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:

Watch out for conflicts like this:

python
@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:

python
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
    return {"file_path": file_path}

Example URLs and values:


URLfile_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:

Example:

python
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:

python
@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:

python
@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:

python
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:

Including Routers in the Main App

main.py:

python
from fastapi import FastAPI
from .users import router as users_router
app = FastAPI()
app.include_router(users_router)

Now your app has:

You can include multiple routers:

python
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:

python
app.include_router(users_router, prefix="/api")

Now the user routes will be:

This is useful when you version your API, for example:

python
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:

Example route combining all three:

python
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:

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:

python
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:

python
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:

python
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:

Summary

In FastAPI:

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

Comments

Please login to add a comment.

Don't have an account? Register now!