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

Add a new field

πŸ“… Adding a Published Date Field to the Book API

In this phase, we will enhance our API by introducing a new field:

πŸ‘‰ published_date

This field will allow us to:

  • Store the year a book was published

  • Validate that the year is within an acceptable range

  • Filter books based on publication year

We will represent published_date as an integer year, for example:

2012

This keeps the system simple while still demonstrating important backend validation concepts.


🧱 Step 1: Update the Book Domain Model

First, we must update our internal domain object.

Remember:

  • Book β†’ Internal business object

  • BookRequest β†’ API boundary model (Pydantic)

πŸ‘‰ We begin by adding published_date to the Book class.

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

πŸ”Ž What Changed?

  • Added published_date: int

  • Updated the constructor to accept published_date

  • Every Book object must now include this field

πŸ‘‰ This ensures consistency across our backend system.


πŸ§ͺ Step 2: Add Validation to BookRequest

Now we update the Pydantic request model.

This is where validation happens before business logic runs.

We will enforce:

  • Year must be greater than 1999

  • Year must be less than 2031

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)

🧠 Why Validate Here?

Because:

  • Validation belongs at the API boundary

  • Bad data should never enter your system

  • FastAPI + Pydantic reject invalid input automatically

If a user submits:

1990

FastAPI will return:

HTTP 422 β€” Unprocessable Entity

πŸ‘‰ And your function will never execute.


🧭 Step 3: Improve Swagger Documentation with an Example

Now let’s guide API users properly.

We’ll add published_date to the schema example.

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

πŸ”Ž What This Does

In Swagger UI:

  • published_date appears in the schema

  • A full example payload is displayed

  • Users understand exactly how to structure their request

πŸ‘‰ This improves developer experience significantly.


πŸ“š Step 4: Update the Sample Data

Now update the in-memory BOOKS list.

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

🧠 Backend Insight

Now your system supports:

  • Storage of publication year

  • Filtering by year

  • Validation before insertion

  • Consistent domain modeling

You are gradually building a properly structured backend.


πŸ” Step 5: Create an Endpoint to Filter by Published Date

We will now create a GET endpoint that filters by publication year.

@app.get("/books/publish/")
async def read_books_by_publish_date(published_date: int): # πŸ‘‰ Query parameter
    books_to_return = []
    for book in BOOKS:
        if book.published_date == published_date:
            books_to_return.append(book)
    return books_to_return

πŸ”Ž What Is Happening Here?

  • published_date is a query parameter

  • FastAPI extracts and validates it as int

  • We loop through the books

  • We return only matching years

Example request:

GET /books/publish/?published_date=2030

🌐 Step 6: Test in the Browser

πŸ‘‰ Open Swagger UI

Try the following:

  1. Search using a valid year (e.g., 2029)

  2. Create a book with 2025

  3. Try invalid years like:

    • 1990

    • 2050

You should receive:

HTTP 422 β€” Unprocessable Entity

πŸ‘‰ This confirms validation is working 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
    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):
    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
            
@app.delete("/books/{book_id}")
async def delete_book(book_id: int):
    for i in range(len(BOOKS)):
        if BOOKS[i].id == book_id:
            BOOKS.pop(i)
            break
            
@app.get("/books/publish/")
async def read_books_by_publish_date(published_date: int):
    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