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

Swagger Pydantic Configurations

๐Ÿงฑ Enhancing Field Validation in BookRequest

In the previous step, we introduced field-level validation inside the BookRequest model using Pydantic.

Now, weโ€™re going to deepen that understanding and improve how our API communicates expectations to the client.

๐Ÿ‘‰ Remember:

  • Validation is not just about preventing bad data.

  • Itโ€™s about clearly defining backend rules.


๐Ÿ“Œ Understanding the Current Validation Rules

Here is the existing model:

class BookRequest(BaseModel): # ๐Ÿ‘‰ pydantic model
    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=0, lt=6)

Letโ€™s break this down properly ๐Ÿ‘‡

  • id field is optional, and its default value is null (None).

  • title must have at least 3 characters

  • author must not be empty

  • description must be between 1 and 100 characters

  • rating must be:

    • Greater than 0

    • Less than 6


๐Ÿง  Backend Fundamental Insight

When a request hits your FastAPI endpoint:

  1. FastAPI reads the request body.

  2. It passes the data into the BookRequest model.

  3. Pydantic validates every field.

  4. If validation fails โ†’ FastAPI automatically returns a 422 Unprocessable Entity response.

This happens before your endpoint function executes.

That is extremely important.

๐Ÿ‘‰ Your route logic only runs if the data passes validation.

This is how FastAPI protects your backend automatically.


๐Ÿ†” Improving the ID Field Description

Currently:

id: Optional[int] = None

This works โ€” but it lacks clarity in the API documentation.

We can improve it like this:

id: Optional[int] = Field(description="ID is not required", default=None)

๐ŸŽฏ Why This Matters

FastAPI automatically generates API documentation using:

  • ๐Ÿง  OpenAPI

  • ๐Ÿ“˜ Swagger UI

By adding a description:

๐Ÿ‘‰ We are improving the API contract clarity.

Now when users view the schema in Swagger UI, they understand:

  • The id exists

  • But it is not required when creating a book

This improves developer experience significantly.


๐Ÿงญ Guiding Users with Model Configuration

We can take things further by adding a request example.

Here is the improved model:

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
            }
        }
    }

๐Ÿ” What Does model_config Do?

model_config is used to customize how the model is displayed in:

  • OpenAPI schema

  • Swagger UI documentation

In this case, it adds a custom example request body to the generated docs.

๐Ÿ‘‰ It does NOT affect data validation

๐Ÿ‘‰ It does NOT change backend behavior

๐Ÿ‘‰ It is only for improving documentation

This highlights an important backend principle:

Validation and documentation are separate concerns.

You are defining both:

  • How the API enforces rules (validation)

  • How easy the API is to understand and use (documentation)

This is a sign of well-designed, professional APIs.


๐ŸŒ Viewing the Example in the Browser

๐Ÿ–ฅ Open your browser

๐Ÿ‘‰ Navigate to: http://127.0.0.1:8000/docs

Under the Create_book (POST) endpoint:

You will notice:

  • โœ” The id field appears in the schema

  • โŒ The id field is NOT included in the example (because not included in json_schema_extra which act as placeholder request body)

๐Ÿค” Why?

Because:

  • The id is auto-generated.

  • The client does not provide it when creating a new book.

This clearly communicates:

  • What the backend expects

  • What the backend controls


๐Ÿง  Backend Concept Reinforcement

Letโ€™s pause and understand something important.

When creating a resource:

  • The client sends data.

  • The backend validates it.

  • The backend assigns an ID.

  • The backend stores it.

This follows a core backend principle:

The server owns resource identity.

Thatโ€™s why id is optional in requests โ€” but required internally.


๐Ÿ—‚๏ธ 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):
    book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
    return book