Path operations are the unit of every FastAPI API, and choosing the right method and path is basic API design.
In FastAPI's vocabulary a path is the URL part after the host (/items/5), an operation is the HTTP method (GET, POST, PUT, PATCH, DELETE), and a path operation is the pair, handled by a path operation function. You declare one with a decorator named after the method: @app.get, @app.post, @app.put, @app.patch, @app.delete (plus head, options and trace).
Use methods for their HTTP meaning, because browsers, proxies, caches and client libraries rely on it:
- GET reads and must not change anything; it may be cached and retried.
- POST creates something or triggers an action.
- PUT replaces a resource; PATCH updates part of it.
- DELETE removes it.
One path can have several operations: GET /items lists, POST /items creates. If no route matches the path, the client gets 404; if the path exists but not for that method, 405 Method Not Allowed. Routes are checked in the order you declare them, which matters when a fixed path like /users/me overlaps a parametrized one like /users/{user_id}: declare the fixed one first.
The function may be def or async def. Start with plain def; FastAPI runs it in a thread pool so slow code doesn't block the server (the async lesson explains when to switch). Whatever you return, a dict, list, string, number or Pydantic model, is converted to JSON.
Decorator arguments configure the operation: status_code=201, tags=["items"] to group it in the docs, summary=..., deprecated=True. The function's docstring becomes the operation's description in the documentation.
The example uses TestClient, which sends requests to the app in-process with no server; the testing lesson covers it properly.
Example
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
items = ["apple", "pear"]
@app.get("/items", tags=["items"])
def list_items():
"""Return every item."""
return items
@app.post("/items", status_code=201, tags=["items"])
def add_item():
items.append("plum")
return {"count": len(items)}
client = TestClient(app)
print(client.get("/items").json()) # ['apple', 'pear']
print(client.post("/items").status_code) # 201
print(client.put("/items").status_code) # 405: no PUT for /items
print(client.get("/nope").status_code) # 404Remember
- A path operation = path + HTTP method + the function that handles it
- Use GET for reads only; POST/PUT/PATCH/DELETE for changes
- Routes match in declaration order: put fixed paths before parametrized ones
- Unknown path gives 404, known path with the wrong method gives 405
Watch out for
- Changing data in a GET handler: crawlers, prefetchers and retries will trigger it.