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

Fetch Books by Rating

⭐ 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_return

Now 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=5

FastAPI automatically:

  1. Extracts book_rating from the query string.

  2. Validates it as an int.

  3. Passes it into the function.

Let’s understand the request lifecycle:

  1. Client sends request → /books/?book_rating=5

  2. FastAPI parses the query string.

  3. It validates book_rating as an integer.

  4. If validation fails → returns a 422 error.

  5. 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>
  1. Locate GET /books/

  2. Click Try it out

  3. Provide a value for book_rating

  4. Click 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