How do you define path operations (GET, POST, PUT, DELETE) in FastAPI?
FastAPI uses decorator methods on the app (or router) instance to define path operations. Each HTTP method has a corresponding decorator:
Available decorators:
@app.get()-- Read resources@app.post()-- Create resources@app.put()-- Full update/replace@app.patch()-- Partial update@app.delete()-- Remove resources@app.options()-- CORS preflight, allowed methods@app.head()-- Like GET but no body@app.trace()-- Diagnostic echo
Decorator parameters:
path(first arg) -- The URL patternstatus_code-- Default response status codetags-- List of tags for docs groupingsummary-- Short description for docsdescription-- Long description (overrides docstring)response_model-- Pydantic model for response filteringdeprecated-- Mark endpoint as deprecatedresponses-- Dict of additional response schemas
Key conventions:
- Return a dict or Pydantic model -- FastAPI auto-serializes to JSON
- POST typically returns 201, DELETE returns 204
- Each function handles exactly one HTTP method (unlike Flask which can handle multiple)
@app.HTTP_METHOD("/path", status_code=CODE, tags=["tag"])
def handler_function(parameters):
# process request
return response_data
# Unlike Flask, each decorator = one HTTP method
# Flask: @app.route('/items', methods=['GET', 'POST'])
# FastAPI: @app.get('/items') and @app.post('/items') separately
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class Task(BaseModel):
title: str
done: bool = False
tasks: dict[int, dict] = {}
counter = 0
@app.get("/tasks/", tags=["tasks"])
def list_tasks():
"""List all tasks."""
return list(tasks.values())
@app.post("/tasks/", status_code=201, tags=["tasks"])
def create_task(task: Task):
"""Create a new task."""
global counter
counter += 1
record = {"id": counter, **task.model_dump()}
tasks[counter] = record
return record
@app.get("/tasks/{task_id}", tags=["tasks"])
def get_task(task_id: int):
"""Get a specific task."""
return tasks.get(task_id, {"error": "not found"})
@app.put("/tasks/{task_id}", tags=["tasks"])
def replace_task(task_id: int, task: Task):
"""Replace an entire task."""
tasks[task_id] = {"id": task_id, **task.model_dump()}
return tasks[task_id]
@app.patch("/tasks/{task_id}", tags=["tasks"])
def update_task(task_id: int, task: Task):
"""Partially update a task."""
if task_id in tasks:
stored = tasks[task_id]
stored.update(task.model_dump(exclude_unset=True))
return stored
return {"error": "not found"}
@app.delete("/tasks/{task_id}", status_code=204, tags=["tasks"])
def delete_task(task_id: int):
"""Delete a task."""
tasks.pop(task_id, None)
client = TestClient(app)
resp = client.post("/tasks/", json={"title": "Buy milk"})
print(f"POST 201: {resp.json()}")
resp = client.get("/tasks/1")
print(f"GET 200: {resp.json()}")
resp = client.put("/tasks/1", json={"title": "Buy oat milk", "done": True})
print(f"PUT 200: {resp.json()}")
resp = client.delete("/tasks/1")
print(f"DELETE: {resp.status_code}")
- •Each HTTP method has its own decorator: @app.get(), @app.post(), @app.put(), @app.patch(), @app.delete()
- •Unlike Flask, each decorator handles exactly one HTTP method
- •status_code parameter sets the default response code (POST=201, DELETE=204 are common)
- •Return dicts or Pydantic models -- FastAPI auto-serializes to JSON
- •PUT replaces the entire resource; PATCH updates only provided fields -- use model_dump(exclude_unset=True) for PATCH
- •DELETE with status_code=204 returns no body -- returning data from a 204 handler is silently ignored
- •Function names must be unique across the app -- they become OpenAPI operation IDs