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

Fields - Data Validation

πŸ›‘οΈ Applying Field-Level Validation with Pydantic

In this phase, we will apply specific validation rules to each field inside the BookRequest model.

πŸ‘‰ Instead of just checking types, we will now enforce constraints such as:

  • Minimum string length

  • Maximum string length

  • Numeric boundaries

To achieve this, we import and use Field from Pydantic.


πŸ“¦ Step 1 β€” Importing Field

from fastapi import FastAPI
from pydantic import BaseModel, Field

🧠 Backend Understanding

Remember:

  • Book is your internal domain model

  • It assumes the data is already trusted

  • Validation must happen before creating this object

That validation will now happen inside BookRequest.


🧱 Step 2 β€” Defining Validation Rules in BookRequest

Now we enhance the request model:

class BookRequest(BaseModel):
    id: int
    title: str = Field(min_length=3)
    author: str
    description: str
    rating: int

πŸ” What Field(min_length=3) Does

title: str = Field(min_length=3)

πŸ‘‰ This ensures the title:

  • Must be at least 3 characters long

  • Cannot be empty or too short

🧠 Important:

This validation happens before your endpoint function runs.

<aside> πŸ’‘

FastAPI β†’ Pydantic β†’ Validation β†’ THEN your function executes.

</aside>


🌐 Step 3 β€” Testing Validation in the Browser

Try creating a book with:

  • Title = "Hi" (less than 3 characters)

You will receive:

HTTP 422 β€” Unprocessable Entity

🧠 Why HTTP 422?

422 means:

  • The request structure was correct

  • The data failed validation rules

This is professional API behavior.


πŸ”Ž Step 4 β€” Expanding Validation Rules

Now let’s apply constraints to multiple fields:

class BookRequest(BaseModel):
    id: int
    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)

πŸ“˜ Field Rule Breakdown

Field

Rule

Meaning

title

min_length=3

Must be at least 3 characters

author

min_length=1

Cannot be empty

description

min_length=1, max_length=100

Between 1 and 100 characters

rating

gt=0, lt=6

Must be between 1 and 5


🌐 Testing Rating Validation

Try creating a book with:

rating = 0

Result:

HTTP 422 β€” Unprocessable Entity

πŸ”’ Step 5 β€” Ensuring Unique Book IDs

Right now, users must manually provide an id.

That’s not ideal.

Instead, we’ll automatically generate unique IDs.

πŸ‘‰ Create an ID Generator Function

def find_book_id(book: Book):
    if len(BOOKS) > 0:
        book.id = BOOKS[-1].id + 1
    else:
        book.id = 1
    return book

🧠 What This Does

  • If books exist β†’ increment last ID

  • If list is empty β†’ start at 1

This ensures:

  • No duplicate IDs

  • Predictable sequential assignment


πŸ”„ Step 6 β€” Apply ID Generation Before Saving

Update your endpoint:

@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))

Code Explanation

  • Any id provided in the request is ignored

  • The find_book_id() function automatically assigns a correct ID

  • If the list is empty, the ID starts from 1

  • Otherwise, the ID is set to the last book’s ID + 1

  • The updated book is then added to the BOOKS list

πŸ’‘ Cleaner Version

def find_book_id(book: Book):
    book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
    return book

🌐 Step 7 β€” Verifying ID Assignment

Create a new book in the browser.

You should see:

  • ID automatically assigned

  • ID incremented properly


🧾 Step 8 β€” Making id Optional

Now we improve usability.

Users should NOT have to supply an ID.

πŸ‘‰ Import Optional

from typing import Optional

πŸ‘‰ Update the Model

class BookRequest(BaseModel):
    id: Optional[int] = 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)

🧠 What This Means

id: Optional[int] = None
  • The field may be missing

  • If missing β†’ default value = None

  • Our ID generator will assign the correct value

Now the system controls ID generation β€” not the user.


🌍 Final Browser Test

Create a book without providing an ID.

It should:

  • Pass validation

  • Auto-generate ID

  • Store correctly


πŸ—‚οΈ 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] = 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)

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):
    book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
    return book