AI Hints
Home›FastAPI Tutorial›Building the API
FastAPI · Lesson 6 of 12

A Complete CRUD API

⏱ 3 min read▶ Video at 12:21🎓 Free
Watch this lesson in the FastAPI courseWatch on YouTube ↗Full FastAPI course

Sweet Crumbs keeps growing, and Maya's staff needs to manage the menu themselves. Add a new pastry. Change a price. Remove something that sold out. Developers call these four jobs CRUD. Create, Read, Update, and Delete. And each one has its own HTTP method. POST, GET, PUT or PATCH, and DELETE.

What does the staff need?
From the video: What does the staff need?
Add a new pastry
Change a price
Remove a sold-out item
C Create
R Read
U Update
POST
GET

A model and a place to store it

We start with a model for a menu item, and a plain dictionary acting as our storage for now. A real database comes in a later lesson. The dictionary holds two items to begin with.

main.pypython
class MenuItem(BaseModel):
    name: str
    price: float
    available: bool = True

menu: dict[int, MenuItem] = {
    1: MenuItem(name="Chocolate Cake", price=18.0),
    2: MenuItem(name="Sourdough Loaf", price=7.0),
}

Read, and say “not found”

Reading is simple. Slash menu returns every item. For a single item, we look it up by ID. And if it does not exist, we raise an HTTP exception, with status code four hundred and four, and a helpful message. FastAPI turns that into a proper JSON error.

main.pypython
@app.get("/menu/")
def list_items():
    return menu

@app.get("/menu/{item_id}")
def read_item(item_id: int):
    if item_id not in menu:
        raise HTTPException(status_code=404,
                            detail="Menu item not found")
    return menu[item_id]
FastAPI turns it into a proper JSON error

Create and read

Let's try it. Create a croissant with POST. We get a two hundred and one, and the new ID is three. List the menu, and there it is. Ask for item ninety nine, and we get a four hundred and four, with our own message.

terminal
curl -s -X POST localhost:8000/menu/ -H 'Content-Type: application/json' -d '{"name":"Croissant","price":4.0}'
{"id":3,"name":"Croissant","price":4.0,"available":true}
curl -s localhost:8000/menu/
{"1":{"name":"Chocolate Cake","price":18.0,"available":true},"2":{"name":"Sourdough Loaf","price":7.0,"available":true},"3":{"name":"Croissant","price":4.0,"available":true}}
curl -s localhost:8000/menu/99
{"detail":"Menu item not found"}
201 Created · the new ID is 3
Item 99 does not exist → 404 + our message

Update: PUT or PATCH

To update, there are two choices. PUT replaces the whole item, so the client sends every field. PATCH changes only some fields. We model that with an update model, where every field is optional. Then model dump, with exclude unset, gives us only the fields the client actually sent.

main.pypython
@app.put("/menu/{item_id}")
def replace_item(item_id: int, item: MenuItem): ...

class MenuItemUpdate(BaseModel):
    name: str | None = None
    price: float | None = None
    available: bool | None = None

menu[item_id] = menu[item_id].model_copy(update=changes.model_dump(exclude_unset=True))

Patch, delete, gone

Patch the croissant price to four fifty, and only the price changes. Delete it, and we get a two hundred and four, which means success, with no content. Ask for it again, and it is gone. Four hundred and four.

terminal
curl -s -X PATCH localhost:8000/menu/3 -H 'Content-Type: application/json' -d '{"price":4.5}'
{"name":"Croissant","price":4.5,"available":true}
curl -s -o /dev/null -w "%{http_code}" -X DELETE localhost:8000/menu/3
204
curl -s localhost:8000/menu/3
{"detail":"Menu item not found"}
Only the price changed
204 No Content = success, nothing to return

The docs list all five endpoints, colour coded by method. You can try every one, including the error cases, right in the browser.

A Complete CRUD API
From the video: A Complete CRUD API
Five endpoints, color-coded by method
Try the error case too: item 99 → 404

The complete code for this lesson

Here is the full main.py exactly as it runs at the end of this lesson. Save it, install FastAPI with pip install "fastapi[standard]", and start it with fastapi dev main.py, then open http://127.0.0.1:8000/docs.

main.pypython
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel

app = FastAPI(title="Sweet Crumbs API")


class MenuItem(BaseModel):
    name: str
    price: float
    available: bool = True


class MenuItemUpdate(BaseModel):
    name: str | None = None
    price: float | None = None
    available: bool | None = None


menu: dict[int, MenuItem] = {
    1: MenuItem(name="Chocolate Cake", price=18.0),
    2: MenuItem(name="Sourdough Loaf", price=7.0),
}
next_id = 3


@app.get("/menu/")
def list_items():
    return menu


@app.get("/menu/{item_id}")
def read_item(item_id: int):
    if item_id not in menu:
        raise HTTPException(status_code=404, detail="Menu item not found")
    return menu[item_id]


@app.post("/menu/", status_code=status.HTTP_201_CREATED)
def create_item(item: MenuItem):
    global next_id
    menu[next_id] = item
    next_id += 1
    return {"id": next_id - 1, **item.model_dump()}


@app.put("/menu/{item_id}")
def replace_item(item_id: int, item: MenuItem):
    if item_id not in menu:
        raise HTTPException(status_code=404, detail="Menu item not found")
    menu[item_id] = item
    return item


@app.patch("/menu/{item_id}")
def update_item(item_id: int, changes: MenuItemUpdate):
    if item_id not in menu:
        raise HTTPException(status_code=404, detail="Menu item not found")
    menu[item_id] = menu[item_id].model_copy(update=changes.model_dump(exclude_unset=True))
    return menu[item_id]


@app.delete("/menu/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int):
    if item_id not in menu:
        raise HTTPException(status_code=404, detail="Menu item not found")
    del menu[item_id]

Summary

That is a full CRUD API. Pair the right method with each job, raise an HTTP exception for missing things, and use status codes that tell the truth. Maya's staff can now manage the menu. But we keep repeating the same checks. In the next lesson, dependencies, and how to stop repeating yourself. See you there.

Key takeaways
  • Match the HTTP method to the job
  • HTTPException for missing things
  • Status codes that tell the truth
Next up: Dependencies — stop repeating yourself