Path operations and HTTP methods

FastAPI · Essentials · 8 min

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)     # 404

Remember

  • 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.

Check yourself

  1. A client sends PUT /items, but only GET and POST are declared for /items. What status code comes back?
  2. You declared @app.get('/users/{user_id}') before @app.get('/users/me'), with user_id: int. What happens on GET /users/me?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: fastapi.tiangolo.com/tutorial/path-operation-configuration/