⭐ Creating an API Endpoint to Filter Books by Rating
In this phase, we will build an endpoint that allows users to filter books by a query parameter.
This introduces another core backend concept:
Filtering a collection using Query Parameters.
Unlike path parameters (which identify a single resource), query parameters are used to refine or filter a collection.
📌 Define the GET Endpoint
Here is the implementation:
@app.get("/books/")
async def read_book_by_rating(book_rating: int): # 👉 query parameter
books_to_return = []
for book in BOOKS:
if book.rating == book_rating:
books_to_return.append(book)
return books_to_returnNow let’s break this down properly 👇
🧭 Understanding Query Parameters
Notice something important:
@app.get("/books/")There is no {} in the route path.
That means book_rating is not a path parameter.
Instead, it is a query parameter.
When you make a request like this:
/books/?book_rating=5FastAPI automatically:
Extracts
book_ratingfrom the query string.Validates it as an
int.Passes it into the function.
Let’s understand the request lifecycle:
Client sends request →
/books/?book_rating=5FastAPI parses the query string.
It validates
book_ratingas an integer.If validation fails → returns a 422 error.
If validation passes → function executes.
Again:
Validation happens before your logic runs.
This is a core backend safety mechanism.
📦 Understanding the Filtering Logic
books_to_return = []
for book in BOOKS:
if book.rating == book_rating:
books_to_return.append(book)Here’s what’s happening:
We iterate over the entire collection.
We compare each book’s rating.
If it matches the requested rating → we add it to a new list.
We return only the filtered results.
🧠 Backend Concept
This simulates a database filtering operation.
In a real production backend, this would look like:
SELECT * FROM books WHERE rating = 5;
# 👉 We will cover this later in the course.Right now, we are filtering an in-memory list.
But conceptually:
Path parameter → fetch one resource
Query parameter → filter many resources
This distinction is extremely important in backend API design.
🌐 Testing the Endpoint in Swagger
Now let’s verify the behavior.
🖥 Open:
<http://127.0.0.1:8000/docs>Locate GET /books/
Click Try it out
Provide a value for
book_ratingClick Execute
If books with that rating exist:
✔ Only those books will be returned.
🗂️ 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))
@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
def find_book_id(book: Book):
book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
return book