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

HTTP Exceptions

⚠️ Understanding HTTP Exceptions

In FastAPI, HTTP Exceptions allow your API to return structured error responses using proper HTTP status codes and meaningful error messages.

Instead of:

  • Failing silently

  • Returning misleading 200 OK responses

  • Crashing with unhandled errors

You can raise a controlled exception that:

  • Stops endpoint execution

  • Returns a specific status code

  • Sends a clear message to the client

For example, if a requested book does not exist, the API should return:

404 Not Found not 200 OK.


🔧 Importing HTTPException

To use HTTP exceptions, import HTTPException from FastAPI:

from typing import Optional
from fastapi import FastAPI, Path, Query, HTTPException 👈
from pydantic import BaseModel, Field

HTTPException gives you direct control over:

  • status_code

  • detail (error message)


🧪 Example — HTTPException in a GET Endpoint

Here’s how we raise an exception when a book does not exist:

# GET Endpoint for finding a book
@app.get("/books/{book_id}")
async def read_book(book_id: int = Path(gt=0)):
    for book in BOOKS:
        if book.id == book_id:
            return book
    raise HTTPException (status_code=404, detail="Item not found")

What Happens Here?

  1. The endpoint loops through BOOKS

  2. If a matching ID is found → return the book

  3. If no match is found → execution reaches raise HTTPException

  4. FastAPI immediately stops execution

  5. The client receives:

{
  "detail": "Item not found"
}

With status code:

404 Not Found

🌐 Testing in the Browser

Using Swagger UI or your browser:

  • Send a GET request to /books/{book_id}

  • Provide an ID that does not exist

Instead of returning 200 OK, the API correctly returns:

404 Not Found

This ensures the response accurately reflects reality.


✏️ Using HTTP Exceptions in PUT and DELETE Requests

HTTP exceptions are equally important in update and delete operations.

🔄 PUT (Update) Method

# PUT request for updating a book.
@app.put("/books/update")
async def update_book(book: BookRequest):
    book_changed = False
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book.id:
            BOOKS[i] = book
            book_changed = True
            break
    if not book_changed:
        raise HTTPException(status_code=404, detail="Item not found")

How This Works

  1. Client submits a book object

  2. System searches for matching ID

  3. Boolean flag tracks if update occurred

  4. If no match is found → raise HTTPException(404)

This prevents an invalid update from returning 200 OK.

🗑️ DELETE Method

The same pattern applies to DELETE operations:

# DELETE request.
@app.delete("/books/{book_id}") 
async def delete_book(book_id: int = Path(gt=0)):
    book_changed = False
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book_id:
            BOOKS.pop(i)
            book_changed = True
            break
    if not book_changed:
        raise HTTPException(status_code=404, detail="Item not found")

If the book does not exist, the API responds correctly with:

404 Not Found 

🌐 Browser Testing Scenarios

Try the following:

  • Update a book that does not exist.

  • Delete a book.

  • Attempt to delete it again.

In all cases, the API returns:

  • ❌ Not 200 OK

  • ✅ 404 — Not Found

This ensures clients are never misled.


🗂️ books2.py

from typing import Optional
from fastapi import FastAPI, Path, Query, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()

class Book:
    id: int
    title: str
    author: str
    description: str
    rating: int
    published_date: int

    def __init__(self, id, title, author, description, rating, published_date):
        self.id = id
        self.title = title
        self.author = author
        self.description = description
        self.rating = rating
        self.published_date = published_date

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=1, lt=6)
    published_date: int = Field(gt=1999, lt=2031)

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

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

@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))
    
@app.get("/books/{book_id}")
async def read_book(book_id: int = Path(gt=0)):
    for book in BOOKS:
        if book.id == book_id:
            return book
    raise HTTPException(status_code=404, detail="Item not found")

@app.get("/books/") 
async def read_book_by_rating(book_rating: int = Query(gt=0, lt=6)):
    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")
async def update_book(book: BookRequest):
    book_changed = False
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book.id:
            BOOKS[i] = book
            book_changed = True
            break
    if not book_changed:
        raise HTTPException(status_code=404, detail="Item not found")
            
@app.delete("/books/{book_id}") 
async def delete_book(book_id: int = Path(gt=0)):
    book_changed = False
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book_id:
            BOOKS.pop(i)
            book_changed = True <---
            break
    if not book_changed:
        raise HTTPException(status_code=404, detail="Item not found")
            
@app.get("/books/publish/")
async def read_books_by_publish_date(
    published_date: int = Query(gt=1999, lt=2031)):
    books_to_return = []
    for book in BOOKS:
        if book.published_date == published_date:
            books_to_return.append(book)
    return books_to_return
    
def find_book_id(book: Book):
    book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
    return book