FastAPI - Beginner to Pro / Project 2- Move Fast With FastAPI

Update Book with PUT Request

✏️ Creating a PUT Endpoint to Update a Book

In this phase, we will implement a PUT endpoint that allows users to update an existing book.

This introduces an important backend concept:

Updating an existing resource using an HTTP PUT method.


📌 Define the PUT Endpoint

Here is the implementation:

@app.put("/books/update_book")
async def update_book(book: BookRequest):
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book.id:
            BOOKS[i] = book
            return BOOKS[i]

Now let’s break this down step by step 👇


🔍 Understanding the Function Logic

👉 The Route

@app.put("/books/update_book")
  • This defines a PUT HTTP method

  • The endpoint path is /books/update_book

  • It signals that we are modifying existing data

👉 The Request Body

async def update_book(book: BookRequest):

Notice:

  • The function expects a BookRequest

  • This means FastAPI will:

    • Parse the JSON body

    • Validate it using Pydantic

    • Convert it into a Python object

⚠ Important:

The request body must include the book’s id.

🧐 Why?

Because the system needs to know which book to update.

🧠 How FastAPI Processes This

When a client sends a PUT request:

  1. JSON body is received.

  2. FastAPI validates it against BookRequest.

  3. If validation fails → 422 error.

  4. If validation succeeds → function executes.

Again:

Validation happens before business logic.


🔄 How the Update Logic Works

for i in range(len(BOOKS)):

We loop through the list using index positions.

🧐 Why use range(len()) instead of for book in BOOKS?

Because we need the index position to replace the object directly.

if BOOKS[i].id == book.id:

We compare:

  • Existing book ID

  • Incoming request book ID

If they match:

BOOKS[i] = book

We replace the old book with the new one.

Then:

return BOOKS[i]

We return the updated resource.


⚠ Important Backend Observation

This is simulating what a database update would look like:

UPDATE books SET title = 'New Title' WHERE id = 3;
# We will discuss this in much detail later on in the course

Right now, we are modifying an in-memory list.

But conceptually:

  • We locate the record.

  • We replace it.

  • We return the updated resource.

This is classic backend CRUD behavior.


🌐 Testing the Endpoint in Swagger

Now let’s verify the behavior.

🖥 Open:

<http://127.0.0.1:8000/docs>
  1. Locate PUT /books/update_book

  2. Click Try it out

  3. Provide:

    • An existing book.

    • Updated title/author, etc.

  4. Click Execute

✔ The book will be updated.


🧠 Improving the Endpoint (Status Codes Concept)

Right now, this endpoint:

  • Always returns 200 OK if it finds the book.

  • Returns [] with 200 OK if no book matches.

This is not ideal in a real backend system.

Later, we will improve this by:

  • Returning 404 Not Found if the book does not exist.

  • Returning proper HTTP status codes.

Professional APIs do not always return 200.

They reflect the actual outcome of the request.


🗂️ books2.py

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Book:
    id: int
    title: str
    author: str
    description: str
    rating: int
    
    def __init__(self, id, title, author, description, rating):
        self.id = id
        self.title = title
        self.author = author
        self.description = description
        self.rating = rating 

class BookRequest(BaseModel):
    id: Optional[int] = Field(description="ID is not required", default=None)
    title: str = Field(min_length=3)
    author: str = Field(min_length=1)
    description: str = Field(min_length=1, max_length=100)
    rating: int = Field(gt=0, lt=6)

    model_config = { 
        "json_schema_extra": {
            "example": {
                "title": "A new book",
                "author": "Name of the author",
                "description": "The description of the book",
                "rating": 5
            }
        }
    }

BOOKS = [
    Book(1, 'Computer Science Pro', 'codingwithrobby', 'A very nice book', 3),
    Book(2, 'Be Fast with FastAPI', 'codingwithrobby', 'A great book', 5),
    Book(3, 'Master Endpoints', 'codingwithrobby', 'An awesome book', 5),
    Book(4, 'HP1', 'Author 1', 'Book Description', 2),
    Book(5, 'HP2', 'Author 2', 'Book Description', 3),
    Book(6, 'HP3', 'Author 3', 'Book Description', 1)
]

@app.get("/books")
async def read_all_books():
    return BOOKS

@app.post("/create_book")
async def create_book(book_request: BookRequest):
    new_book = Book(**book_request.model_dump())
    BOOKS.append(find_book_id(new_book))

def find_book_id(book: Book):
    if len(BOOKS) > 0:
        book.id = BOOKS[-1].id + 1
    else:
        book.id = 1
    return book
    
@app.get("/books/{book_id}") 
async def read_book(book_id: int):
    for book in BOOKS:
        if book.id == book_id:
            return book

@app.get("/books/")
async def read_book_by_rating(book_rating: int):
    books_to_return = []
    for book in BOOKS:
        if book.rating == book_rating:
            books_to_return.append(book)
    return books_to_return
    
@app.put("/books/update_book")
async def update_book(book: BookRequest):
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book.id:
            BOOKS[i] = book
         
def find_book_id(book: Book):
    book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
    return book