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

Pydantic Book Request Validation

πŸ”„ Replace Body() With Pydantic Validation (POST)

Earlier, we used Body() to read request data.

But it did not provide validation or type safety.

πŸ‘‰ Using Pydantic ensures incoming data is:

  • βœ… Properly formatted

  • βœ… Strongly typed

  • βœ… Automatically validated


πŸ“¦ Step 1 β€” Importing Pydantic

FastAPI includes Pydantic by default β€” no extra installation is required.

from fastapi import FastAPI, Body # we have removed Body
from pydantic import BaseModel # πŸ‘‰ FastAPI includes Pydantic by default

πŸ“˜ Step 2 β€” Existing Book Class (Internal Object)

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

πŸ‘‰ This is a plain Python class used inside the app.

  • βœ… Represents a trusted object

  • ❌ No validation

  • ❌ No automatic type checking


🧱 Step 3 β€” Create Pydantic Request Model

class BookRequest(BaseModel):
    id: int
    title: str
    author: str
    description: str
    rating: int

πŸ‘‰ This defines the structure of incoming JSON data.

FastAPI will automatically:

  • Validate data types

  • Reject invalid/missing fields

  • Return HTTP 422 on errors

  • Show schema in Swagger UI

πŸ’‘ Think of it as an API boundary guard.


πŸ” Step 4 β€” Updating the POST Endpoint

Previously, we used Body().

Now, we replace it with a typed parameter:

❌ # Old version using Body()
@app.post("/create-book")
async def create_book(book_request=Body()): 
		BOOKS.append(book_request)

βœ… # New version using Pydantic
@app.post("/create_book")
async def create_book(book_request: BookRequest): 
    BOOKS.append(book_request)

🌐 Testing in the Browser

POST request with Pydantic in Swagger UI

πŸ” What Just Happened?

When a request is sent:

πŸ‘‰ FastAPI will:

  1. Parse the JSON body

  2. Convert it into a BookRequest object

  3. Validate it

  4. Reject it if invalid

⚠️ Problem: book_request is a BookRequest object, not a Book.

Mixing types in BOOKS leads to inconsistent data.


πŸ§ͺ Verifying the Request Type

Let’s confirm what FastAPI is actually passing into our function.

@app.post("/create_book")
async def create_book(book_request: BookRequest):
    print(type(book_request)) # πŸ‘‰ confirms type of book_request. 
    BOOKS.append(book_request)

🌐 Testing in the Browser

Printing and verifying BookRequest type

When you send a request, the terminal will display:

<class 'books2.BookRequest'> # πŸ‘‰ this means its of type BookRequest.

This confirms:

  • βœ… Request body was parsed

  • βœ… Data was validated

  • βœ… We received a Pydantic model instance

Not a dictionary.


πŸ”„ Step 5 β€” Convert to Book Object

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

πŸ“˜ What’s Happening?

πŸ‘‰ model_dump()

Converts the Pydantic model into a dictionary.

book_request.model_dump()

(In Pydantic v2, this replaces .dict())

πŸ‘‰ * (Dictionary Unpacking)

Book(**data)

Is equivalent to:

Book(id=data["id"],
    title=data["title"],
    author=data["author"],
    description=data["description"],
    rating=data["rating"]
)

πŸ§ͺ Verifying the Conversion

Let’s confirm we are now storing a Book object.

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

🌐 Testing in the Browser

Verifying Book object type after conversion

When you send a request, the terminal will display:

<class 'books2.Book'>

This confirms:

  • βœ… Validation occurred

  • βœ… Data was converted

  • βœ… Only Book objects are stored


🧠 Why This Architecture Matters

You now have a clear separation between:

Layer

Purpose

BookRequest

API boundary validation

Book

Internal business object

This improves:

  • πŸ”’ Data integrity

  • πŸ“ˆ Scalability

  • 🧩 Maintainability

  • πŸ— Professional architecture


πŸ—‚οΈ books2.py

from fastapi import FastAPI
from pydantic import BaseModel

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: int
    title: str
    author: str
    description: str
    rating: int

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(new_book)