π 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:
2012This 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 objectBookRequestβ 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: intUpdated the constructor to accept
published_dateEvery
Bookobject 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:
1990FastAPI 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_dateappears in the schemaA 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_dateis a query parameterFastAPI extracts and validates it as
intWe 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:
Search using a valid year (e.g.,
2029)Create a book with
2025Try invalid years like:
19902050
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