FastAPI - Beginner to Pro / API Request Methods

Delete Request (Todo Project)

πŸ—‘οΈ Deleting a Todo (DELETE Request)

In this session, we implement the final CRUD operation:

❌ Delete β€” removing an existing record using the DELETE HTTP method

This will permanently remove a Todo from the database.


πŸ—οΈ Step 1: Create the DELETE Endpoint

Add this below your existing routes:

πŸ—‚οΈ main.py

@app.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(
    db: db_dependency,
    todo_id: int = Path(gt=0)):

What This Endpoint Does

  • Receives todo_id from the URL

  • Validates it must be greater than 0

  • Deletes the matching record

  • Returns 204 No Content on success


πŸ”Ž Step 2: Retrieve the Existing Todo

Before deleting, we must check if the record exists.

πŸ—‚οΈ main.py

todo_model = db.query(Todos).filter(Todos.id == todo_id).first()

Why we use .first():

  • Returns the first matching record

  • Returns None if no record exists

  • Prevents accidental deletion attempts


❗ Step 3: Handle Missing Records

If the Todo does not exist:

πŸ—‚οΈ main.py

if todo_model is None:
    raise HTTPException(status_code=404, detail="Todo not found.")

This keeps our API predictable and REST-compliant.


πŸ—‘οΈ Step 4: Delete the Record

Now we delete the object.

βœ… Recommended Approach (Cleaner & Safer)

πŸ—‚οΈ main.py

db.delete(todo_model)
db.commit()

Why This Is Better

Instead of calling:

πŸ—‚οΈ main.py

db.query(Todos).filter(...).delete()

Using:

πŸ—‚οΈ main.py

db.delete(todo_model)
  • Operates on the tracked object

  • Keeps behavior consistent with how SQLAlchemy manages state

  • Aligns with how we handled UPDATE


🧠 Backend Concept: What Happens Internally?

When you call:

πŸ—‚οΈ main.py

db.delete(todo_model)

SQLAlchemy:

  1. Marks the object for deletion in the session

  2. Prepares a SQL DELETE statement

  3. Executes it when commit() is called

Without commit(), nothing is removed.


🧩 Final Clean Implementation

πŸ—‚οΈ main.py

@app.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(
    db: db_dependency,
    todo_id: int = Path(gt=0)):
    todo_model = db.query(Todos).filter(Todos.id == todo_id).first()

    if todo_model is None:
        raise HTTPException(status_code=404, detail="Todo not found.")

    db.delete(todo_model)
    db.commit()

πŸ§ͺ Testing the DELETE Endpoint

Run your server:

uvicorn main:app --reload

Then open:

/docs

You should now see:

  • GET /

  • GET /todo/{todo_id}

  • POST /todo

  • PUT /todo/{todo_id}

  • DELETE /todo/{todo_id}

πŸ”Ž Test Scenarios

1️⃣ Delete existing ID β†’ 204 No Content

2️⃣ Delete non-existing ID β†’ 404 Not Found

3️⃣ Use negative ID β†’ 422 Validation Error


πŸ—‚οΈ main.py

from typing import Annotated
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from fastapi import FastAPI, Path, Depends, HTTPException
from starlette import status

import models
from models import Todos
from database import engine, SessionLocal

app = FastAPI()

models.Base.metadata.create_all(bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

db_dependency = Annotated[Session, Depends(get_db)]

class TodoRequest(BaseModel):
    title: str = Field(min_length=3)
    description: str = Field(min_length=3, max_length=100)
    priority: int = Field(gt=0, lt=6)
    complete: bool

@app.get("/", status_code=status.HTTP_200_OK)
async def read_all(db: db_dependency):
    return db.query(Todos).all()

@app.get("/todo/{todo_id}", status_code=status.HTTP_200_OK)
async def read_todo(db: db_dependency, todo_id: int = Path(gt=0)):
    todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
    if todo_model is not None:
        return todo_model
    else:
        raise HTTPException(status_code=404, detail='Todo Not Found')

@app.post("/todo/", status_code=status.HTTP_201_CREATED)
async def create_todo(db: db_dependency, todo_request: TodoRequest):
    todo_model = Todos(**todo_request.model_dump())
    db.add(todo_model)
    db.commit()
    
@app.put("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def update_todo(
    db: db_dependency,
    todo_request: TodoRequest,
    todo_id: int = Path(gt=0)):
    todo_model = db.query(Todos).filter(Todos.id == todo_id).first()

    if todo_model is None:
        raise HTTPException(status_code=404, detail="Todo not found.")

    todo_model.title = todo_request.title
    todo_model.description = todo_request.description
    todo_model.priority = todo_request.priority
    todo_model.complete = todo_request.complete

    db.commit()
    
@app.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(
    db: db_dependency,
    todo_id: int = Path(gt=0)):
    todo_model = db.query(Todos).filter(Todos.id == todo_id).first()

    if todo_model is None:
        raise HTTPException(status_code=404, detail="Todo not found.")

    db.delete(todo_model)
    db.commit()

No Changes in models.py and database.py