Handling Different HTTP Methods (GET, POST, PUT, DELETE) · learncode.live

HTTP Methods in FastAPI

FastAPI provides a decorator for every standard HTTP method. Each maps directly to a CRUD operation:

MethodDecoratorCRUDIdempotentRequest Body
GET@app.get()ReadYesNo
POST@app.post()CreateNoYes
PUT@app.put()ReplaceYesYes
PATCH@app.patch()Partial UpdateNoYes
DELETE@app.delete()DeleteYesOptional

Building a CRUD API with FastAPI

Let’s build a task management API that demonstrates every HTTP method:

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
from typing import Optional

app = FastAPI(title="Task Manager API")

# ── Pydantic Models ──

class TaskBase(BaseModel):
    title: str
    completed: bool = False

class TaskCreate(TaskBase):
    pass

class TaskUpdate(BaseModel):
    title: Optional[str] = None
    completed: Optional[bool] = None

class Task(TaskBase):
    id: int

# ── In-Memory Store ──

tasks_db = {}
next_id = 1

GET - Retrieve Resources

@app.get("/tasks", response_model=list[Task])
def list_tasks(completed: Optional[bool] = None):
    """Return all tasks, optionally filtered by completion status."""
    if completed is None:
        return list(tasks_db.values())
    return [t for t in tasks_db.values() if t.completed == completed]


@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
    """Return a single task by ID."""
    task = tasks_db.get(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return task

Status codes for GET:

ScenarioCode
Resource found200 OK
Resource not found404 Not Found
Invalid ID format422 Unprocessable Entity

POST - Create Resources

Use status_code to signal 201 Created:

@app.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(payload: TaskCreate):
    global next_id
    task = Task(id=next_id, **payload.model_dump())
    tasks_db[next_id] = task
    next_id += 1
    return task
curl -X POST http://localhost:8000/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn FastAPI"}'

# 201 Created
# {"id":1,"title":"Learn FastAPI","completed":false}
ScenarioCode
Resource created201 Created
Validation failure422 Unprocessable Entity

PUT - Full Replacement

PUT replaces the entire resource. Missing fields get reset:

@app.put("/tasks/{task_id}", response_model=Task)
def replace_task(task_id: int, payload: TaskCreate):
    """Fully replace a task - requires all fields."""
    if task_id not in tasks_db:
        raise HTTPException(status_code=404, detail="Task not found")
    task = Task(id=task_id, **payload.model_dump())
    tasks_db[task_id] = task
    return task
curl -X PUT http://localhost:8000/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "Master FastAPI", "completed": true}'

PUT vs PATCH: PUT replaces the whole resource. If you omit completed, it resets to the model default (False). Use PATCH for partial updates.

PATCH - Partial Update

PATCH only modifies the fields you send:

@app.patch("/tasks/{task_id}", response_model=Task)
def patch_task(task_id: int, payload: TaskUpdate):
    """Partial update - only supplied fields change."""
    if task_id not in tasks_db:
        raise HTTPException(status_code=404, detail="Task not found")

    existing = tasks_db[task_id]
    update_data = payload.model_dump(exclude_unset=True)
    updated = existing.model_copy(update=update_data)
    tasks_db[task_id] = updated
    return updated

exclude_unset=True ensures only the fields the client actually sent are applied. Fields with None values in TaskUpdate are untouched.

# Only update the title, leave completed as-is
curl -X PATCH http://localhost:8000/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "FastAPI Advanced"}'

DELETE - Remove Resources

@app.delete("/tasks/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_task(task_id: int):
    """Delete a task. Returns 204 No Content on success."""
    if task_id not in tasks_db:
        raise HTTPException(status_code=404, detail="Task not found")
    del tasks_db[task_id]
curl -X DELETE http://localhost:8000/tasks/1
# 204 No Content (empty body)

curl -X DELETE http://localhost:8000/tasks/999
# 404 Not Found
# {"detail":"Task not found"}
ScenarioCode
Deleted successfully204 No Content
Resource not found404 Not Found

The response_model Parameter

FastAPI uses response_model to filter and document the response:

@app.get("/tasks", response_model=list[Task], response_model_exclude_unset=True)
def list_tasks():
    ...
ParameterEffect
response_modelSchema for serialization & OpenAPI docs
response_model_exclude_unsetOmit default values from response
response_model_includeOnly include specific fields
response_model_excludeExclude specific fields
@app.get("/tasks/{task_id}", response_model=Task, response_model_include={"id", "title"})
def get_task(task_id: int):
    """Only returns id and title - no completed field."""
    ...

Generic Route with @app.api()

Use @app.api() when you need to register the same handler under multiple methods:

@app.api_route("/tasks/{task_id}", methods=["GET", "DELETE"])
def handle_task(task_id: int, method: str = "GET"):
    if method == "DELETE":
        if task_id not in tasks_db:
            raise HTTPException(status_code=404)
        del tasks_db[task_id]
        return None
    task = tasks_db.get(task_id)
    if not task:
        raise HTTPException(status_code=404)
    return task

Choosing the Right Status Code

Client ──GET─────► 200 OK
Client ──POST────► 201 Created
Client ──PUT─────► 200 OK
Client ──PATCH───► 200 OK
Client ──DELETE──► 204 No Content
                   │
                   ▼
              Validation Error ──► 422
              Not Found        ──► 404

Key Takeaways

  • FastAPI provides @app.get(), @app.post(), @app.put(), @app.patch(), @app.delete() - one for each HTTP method
  • Use status_code=status.HTTP_201_CREATED for POST and status.HTTP_204_NO_CONTENT for DELETE
  • response_model controls serialization, filtering, and OpenAPI schema generation
  • PUT replaces the entire resource; PATCH only modifies sent fields (use exclude_unset=True)
  • Always return meaningful HTTP status codes - 200, 201, 204, 404, 422
  • Raise HTTPException for error responses instead of returning dicts manually
Courses