π Get All Books by a Specific Author
π― Retrieve all books written by a specific author using:
A Path Parameter
A Query Parameter
π― Expected Behavior
Hereβs what our endpoint should do:
π If we type Author Two β return 2 books
π If we type Author One β return 1 book
π If we type a non-existent author β return 0 books
We will first implement the Path Parameter solution.
π£οΈ Approach 1 β Using a Path Parameter
We will create a new endpoint that accepts the author's name directly in the URL path.
ποΈ books.py
# endpoint that accepts the author's name directly in the URL path.
@app.get("/books/byauthor/{author}")
async def read_books_by_author_path(author: str):
books_to_return = []
for book in BOOKS:
if book.get('author').casefold() == author.casefold():
books_to_return.append(book)
return books_to_returnπ Step-by-Step Explanation
π 1. Define the Route
"/books/byauthor/{author}"{author}is a dynamic path parameterWhatever value is passed in that position becomes the
authorvariable
π 2. Define the Function Parameter
async def read_books_by_author_path(author: str): authoris typed as a stringFastAPI automatically extracts it from the URL
π 3. Create a Filtered List
books_to_return = []We create an empty list to store matching books.
π 4. Loop Through BOOKS
for book in BOOKS:We iterate through all books.
π 5. Case-Insensitive Comparison
book.get('author').casefold() == author.casefold()π§ Why use casefold()?
Ensures case-insensitive comparison
"Author Two" and "author two" will match
π 6. Return the Results
return books_to_returnWe return all books that match the author.
βΆοΈ Running the Application
Open your terminal and start the server:
uvicorn books2:app --reloadπ Testing in the Browser
Steps:
Go to
/docsLocate:
GET /books/byauthor/{author}Click Try it out
Enter
Author TwoClick Execute
β You should see 2 books
Try:
Author Oneβ 1 bookUnknown Authorβ 0 books (an empty list)
π Approach 2 β Switching to a Query Parameter
Now, letβs modify the endpoint to use a query parameter instead.
π To do this, remove {author} from the path.
When there is no dynamic path parameter, FastAPI automatically treats function parameters as query parameters.
After modifying:
@app.get("/books/byauthor/")
async def read_books_by_author_path(author: str):Now author becomes a query parameter.
β Unexpected Error β βUnprocessable Entityβ
If you refresh Swagger and test:
/books/byauthor/?author=Author TwoYou may see:
422 Unprocessable Entityπ€ Why is this happening?
This brings us to a very important concept.
β οΈ Critical Concept β ORDER MATTERS in FastAPI
This endpoint:
@app.get("/books/byauthor/")Conflicts with this one:
@app.get("/books/{book_author}/")
async def read_author_and_category(book_author: str, category: str):π§ Why the Conflict Happens
FastAPI reads routes from top to bottom.
When you define:
@app.get("/books/{book_author}/") <---It matches ANY dynamic value after /books/.
So when you type:
/books/byauthorFastAPI thinks:
book_author = "byauthor"And expects another parameter (category).
When it doesn't receive it:
β It throws a validation error.
π Fix β Fixing the Route Order
To solve this:
π Move the more specific route to the top.
β Before (Incorrect Order)
ποΈ books.py
@app.get("/books/{book_author}/")
...
@app.get("/books/byauthor/{author}")β After (Correct Order)
ποΈ books.py
@app.get("/books/byauthor/")
...
@app.get("/books/{book_author}/")π§ Rule of Thumb:
Always place more specific endpoints above more generic ones
π Testing Again in the Browser
Now:
GET /books/byauthor/{author}worksGET /books/{book_author}/still worksFiltering by category also works
Everything functions correctly.
π§ Why This Matters
FastAPI processes routes chronologically.
If a general route appears first, it can:
Intercept requests
Consume dynamic values
Cause validation failures
This is especially important when working in a single file.
π Path vs Query Parameter
Type | Example | When to Use |
|---|---|---|
Path Parameter |
| Required identifier |
Query Parameter |
| Optional filtering |
π¨ FastAPI Rule to Remember
Best Practice | Reason |
|---|---|
Place specific routes first | Prevent route conflicts |
Place generic routes later | Avoid unintended matches |
Always test after reordering | Ensure correct routing |
ποΈ books.py
from fastapi import FastAPI, Body
app = FastAPI()
BOOKS = [
{"title": "Title One", "author": "Author One", "category": "science"},
{"title": "Title Two", "author": "Author Two", "category": "science"},
{"title": "Title Three", "author": "Author Three", "category": "history"},
{"title": "Title Four", "author": "Author Four", "category": "math"},
{"title": "Title Five", "author": "Author Five", "category": "math"},
{"title": "Title Six", "author": "Author Two", "category": "math"}
]
@app.get("/books")
async def read_all_books():
return BOOKS
@app.get('/books/{book_title}')
async def read_book(book_title: str):
for book in BOOKS:
if book.get('title').casefold() == book_title.casefold():
return book
@app.get("/books/")
async def read_category_by_query(category: str):
books_to_return = []
for book in BOOKS:
if book.get('category').casefold() == category.casefold():
books_to_return.append(book)
return books_to_return
@app.get("/books/byauthor/")
async def read_books_by_author_path(author: str):
books_to_return = []
for book in BOOKS:
if book.get('author').casefold() == author.casefold():
books_to_return.append(book)
return books_to_return
@app.get("/books/{book_author}/")
async def read_author_and_category(book_author: str, category: str):
books_to_return = []
for book in BOOKS:
print('book', book)
if book.get('author').casefold() == book_author.casefold() and \\
book.get('category').casefold() == category.casefold():
books_to_return.append(book)
return books_to_return
@app.post("/books/create_book")
async def create_book(new_book=Body()):
BOOKS.append(new_book)
@app.put("/books/update_book")
async def update_book(updated_book=Body()):
for i in range(len(BOOKS)):
if BOOKS[i].get('title').casefold() == \\
updated_book.get('title').casefold():
BOOKS[i] = updated_book
@app.delete("/books/delete_book/{book_title}")
async def delete_book(book_title: str):
for i in range(len(BOOKS)):
if BOOKS[i].get('title').casefold() == book_title.casefold():
BOOKS.pop(i)
break