Build a Book Collection API with FastAPI
Build a REST API with full CRUD operations using FastAPI and Pydantic.
Introduction
30 Second Summary
Every app you use daily has an API working behind the scenes. When you browse books on an online store, check your library account, or update a reading list, an API is handling those requests.
In this project, you will build a Book Collection API using Python and FastAPI. You will create endpoints that let you add books, browse your collection, update reading statuses, and remove entries.
What You'll Build
A fully functional REST API that manages a personal book collection with full create, read, update, and delete operations, powered by Python type hints and automatic data validation.
By the end of this project, you'll have:
- A running FastAPI application with 5 API endpoints for managing your book collection.
- Automatic interactive documentation (Swagger UI) that lets you test every endpoint directly in the browser.
- Validated request data using Pydantic models with zero manual validation code.
- Secret Mission: Add a rating system that lets you score books and find your top-rated reads.
Are there any prerequisites?
You only need basic Python knowledge (variables, functions, dictionaries, and lists). No prior web development or API experience is required.
Set Up Your Python Environment
Before you can build your Book Collection API, you need a working Python environment on your machine.
In this step, you'll verify that Python is installed, create an isolated project folder with a virtual environment, and install FastAPI.
In this step, get ready to:
- Verify Python 3.10+ is installed on your machine.
- Create a project folder with a virtual environment.
- Install FastAPI and verify the installation.
Verify Python is installed
FastAPI requires Python 3.10 or higher. Let's check which version you have.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.10 or higher
You should see something like Python 3.10.x or higher. You're all set to continue.
ⓧ I see an older version
If you see a version below 3.10 (for example Python 3.9.7), you need to install a newer version.
macOS often ships with an older Python that stays even after you install a newer one. The python3 command may still point to the old version.
- Download the latest Python 3.10+ installer from python.org/downloads.
- Run the installer and follow the prompts.
- On macOS, after installing, try running python3.12 --version (or whichever version you installed) to confirm the new version is available. Use that versioned command for the rest of this project.
- On Windows, the installer includes an option to Add Python to PATH. Make sure this is checked, then reopen PowerShell and run python --version again.
ⓧ Command not found
Python is not installed on your machine yet. Let's install it.
- Go to python.org/downloads and download the latest Python installer for your operating system.
- Run the installer.
- On Windows, check the box that says Add Python to PATH before clicking Install Now.
- Close and reopen your terminal after installation.
- Run the version check command again to confirm it works.
Still not working after installing?
On macOS, try python3.12 --version (using the specific version number you installed). On Windows, try closing all terminal windows, reopening PowerShell, and running python --version.
If neither works, help me troubleshoot my Python installation.
Create your project folder and virtual environment
A virtual environment keeps your project's packages separate from your system Python. This prevents version conflicts between projects.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir book-api
cd book-api
- Confirm the folder was created by running:
pwd
You should see a path ending in book-api.
- Create and activate a virtual environment by running these commands:
macOS
python3 -m venv venv
source venv/bin/activate
Windows
python -m venv venv
venv\Scripts\Activate.ps1
Seeing a script execution error?
PowerShell may block scripts by default. Run this command to allow activation, then try the activate command again:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated copy of Python for your project. Any packages you install while it's active only exist inside this environment.
This means you can install FastAPI here without affecting other Python projects on your machine.
Don't see (venv) in your prompt?
Make sure you ran the activate command (not just the python3 -m venv venv part). The creation command builds the environment. The activate command switches into it.
If you're still stuck, help me activate my Python virtual environment.
Install FastAPI
With your virtual environment active, you can now install FastAPI and all its standard dependencies.
- Install FastAPI with its standard extras by running:
pip install "fastapi[standard]"
What does fastapi[standard] include?
The [standard] extra installs everything you need for development. This includes uvicorn (the server that runs your API), tools for request validation, and the FastAPI CLI.
You'll see pip downloading and installing several packages. This may take a minute.
- Verify that FastAPI installed correctly by running:
macOS
python3 -c "import fastapi; print(fastapi.__version__)"
Windows
python -c "import fastapi; print(fastapi.__version__)"
You should see a version number printed, such as 0.136.3. This confirms FastAPI is installed and importable.
Seeing a ModuleNotFoundError?
This usually means pip installed FastAPI outside your virtual environment. Check that (venv) appears in your terminal prompt. If it doesn't, activate the environment first and run the install command again.
If you're still stuck, help me fix my FastAPI installation.
Your Python environment is ready to go. Next, you'll create your first API endpoint and see FastAPI's automatic documentation in action.
Create Your First Endpoint
Your Python environment is ready with FastAPI installed. Now it's time to write some code and see your API come to life.
An API endpoint is a URL that a server responds to when someone visits it. You'll create one endpoint, start your server, and discover FastAPI's auto-generated documentation.
In this step, get ready to:
- Create your FastAPI application file.
- Start the development server.
- Explore the auto-generated Swagger UI documentation.
Create your FastAPI application file
Every FastAPI project starts with a Python file that creates an application instance and defines at least one endpoint.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code (or Cursor) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Select the book-api folder on your Desktop, then click Open.
- In the Explorer sidebar on the left, hover over the BOOK-API heading and click the new file icon.
- Name the file main.py and press Enter.
- Add the following code to main.py:
from fastapi import FastAPI
# Create the FastAPI application instance
app = FastAPI(title="Book Collection API")
# Define a route for the root URL
@app.get("/")
def read_root():
return {"message": "Welcome to the Book Collection API"}
What does this code do?
- from fastapi import FastAPI imports the main class from the FastAPI library.
- app = FastAPI(title="Book Collection API") creates your application instance. The title shows up in the auto-generated documentation.
- @app.get("/") is a decorator that tells FastAPI to call read_root() when someone sends a GET request to the root URL (/).
- The function returns a Python dictionary, which FastAPI automatically converts to a JSON response.
- Save main.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm the file exists by switching to your terminal and running:
ls
You should see main.py and venv listed in the output.
Don't see main.py listed?
- Make sure you saved the file inside the book-api folder, not a parent directory. Check the file tab in VS Code to confirm the path.
- Check that the filename is exactly main.py with no extra extensions like .py.txt.
Why isn't my main.py file showing up?
Start the development server
FastAPI includes a built-in development server that auto-reloads when you change your code. This means you can edit main.py and see changes instantly without restarting.
- Start the development server by running this command in your terminal:
fastapi dev main.py
What does this command do?
The fastapi dev command starts a local web server with auto-reload enabled. It automatically detects the app object inside main.py and serves your API at http://127.0.0.1:8000.
- Look for the line that says Server started at http://127.0.0.1:8000 and Application startup complete in the terminal output.
- Open your browser and visit http://127.0.0.1:8000.
- You should see a JSON response: {"message":"Welcome to the Book Collection API"}.
Server not starting?
- Make sure your virtual environment is still activated. You should see (venv) at the beginning of your terminal prompt.
- Check that you're running the command from inside the book-api folder where main.py lives.
- If you see a "command not found" error, try reactivating the virtual environment with source venv/bin/activate (macOS/Linux) or venv\Scripts\activate (Windows).
Why won't my FastAPI development server start?
Explore the auto-generated Swagger UI
FastAPI automatically generates interactive API documentation from your Python code. No extra setup or configuration needed.
- Visit http://127.0.0.1:8000/docs in your browser.
- You should see an interactive documentation page titled Book Collection API with your GET / endpoint listed.
- Click the GET / row to expand it.
- Click Try it out.
- Click Execute.
- You should see the JSON response {"message":"Welcome to the Book Collection API"} appear in the Response body section below.
What is Swagger UI?
Swagger UI is an interactive documentation tool that FastAPI generates automatically from your Python type hints and function definitions. Every endpoint you create appears here with a form to test it.
You never have to write documentation by hand. As you add more endpoints in the next steps, they'll appear in this page automatically.
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and keep the dev server running in your terminal.
ⓧ I'd like to double check the full code
Here's the complete main.py file so far:
from fastapi import FastAPI
# Create the FastAPI application instance
app = FastAPI(title="Book Collection API")
# Define a route for the root URL
@app.get("/")
def read_root():
return {"message": "Welcome to the Book Collection API"}
Your API is live and documented. Next up, you'll define a data model for books and add sample data to your collection.
Define Your Book Data Model
Your API server is running and already responding to requests. Now it needs something meaningful to serve: a book collection with real structure.
Every API needs a clear definition of what data it accepts and returns. FastAPI uses Pydantic models to define that shape, turning Python type hints into automatic data validation.
In this step, get ready to:
- Define a Book data model with Pydantic.
- Define a BookUpdate model for partial updates.
- Add sample books so your API has data to return.
Create the Book data model
When someone sends book data to your API, you need a way to check that it includes the right fields with the right types. Pydantic's BaseModel lets you declare that shape once, and FastAPI uses it to validate every incoming request automatically.
- In main.py, add this import on the line directly below from fastapi import FastAPI:
from pydantic import BaseModel
- Below the app = FastAPI(title="Book Collection API") line, add the Book class:
class Book(BaseModel):
# Required fields - every book needs these
title: str
author: str
year: int
# Default value makes this field optional in requests
status: str = "unread"
What does this code do?
- BaseModel is the Pydantic class that gives your model automatic validation powers.
- title: str means the title field must be a string. Same pattern applies to author and year.
- status: str = "unread" sets a default value. If someone creates a book without specifying a status, it automatically becomes "unread".
- Save main.py.
- If your dev server is still running, you'll see it reload in the terminal. Look for the line Application startup complete to confirm there are no errors.
Seeing an import error?
- Make sure you spelled pydantic with a lowercase p. It ships with FastAPI so it's already installed.
- Check that the import line is on its own line directly below from fastapi import FastAPI.
- Why is my Pydantic import failing in my FastAPI project?
Add the BookUpdate model
When updating a book, you don't always want to change every field. A separate update model with all optional fields lets your API accept partial updates.
- In main.py, add the BookUpdate class directly below the Book class:
class BookUpdate(BaseModel):
# All fields optional - only update what's provided
title: str | None = None
author: str | None = None
year: int | None = None
status: str | None = None
What does str | None = None mean?
str | None means the field accepts either a string or None. The = None sets the default to None, making the field completely optional.
Together, this pattern means "include this field only if you want to change it." Fields the client doesn't send stay unchanged.
- Save main.py.
- You won't see a visible change yet. The next chunk connects these models to real data.
Seeing a syntax error on the | symbol?
The str | None syntax requires Python 3.10 or higher. Run python3 --version to check your version. If you're on an older version, revisit Step 1 to upgrade Python.
Why am I getting a syntax error with str | None in my Pydantic model?
Add sample books to your collection
Your models define the shape of a book, but your API needs actual data to serve. An in-memory list works perfectly for learning. It resets every time you restart the server, keeping things simple.
- In main.py, add this books list below the BookUpdate class:
# Sample books to start with
books = [
{"id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "status": "read"},
{"id": 2, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960, "status": "unread"},
{"id": 3, "title": "1984", "author": "George Orwell", "year": 1949, "status": "reading"},
]
What does this code do?
- Each book is a dictionary with an id field for identification, plus the same fields from your Book model.
- The three sample books have different statuses (read, unread, reading) so you can test filtering later.
- This data lives in memory. Restarting the server resets it back to these three books.
- Now update the root endpoint to report how many books are in the collection.
- Find this line inside the read_root function:
return {"message": "Welcome to the Book Collection API"}
- Replace it with this line that includes the book count:
return {"message": "Welcome to the Book Collection API", "total_books": len(books)}
- Save main.py.
- If your dev server is stopped, start it again by running:
fastapi dev main.py
- Visit http://127.0.0.1:8000 in your browser. You should see the JSON response now includes "total_books": 3.
Not seeing total_books in the response?
- Make sure the books list is defined ABOVE the read_root function. Python reads top to bottom, so len(books) won't work if books is defined after the function.
- Check that you typed len(books) exactly, not len(book) or len(Books).
- Why isn't total_books showing in my FastAPI root endpoint response?
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and confirmed total_books shows in your browser.
ⓧ I'd like to double check the full code
Here's the complete main.py at this point:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Book Collection API")
class Book(BaseModel):
title: str
author: str
year: int
status: str = "unread"
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
year: int | None = None
status: str | None = None
books = [
{"id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "status": "read"},
{"id": 2, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960, "status": "unread"},
{"id": 3, "title": "1984", "author": "George Orwell", "year": 1949, "status": "reading"},
]
@app.get("/")
def read_root():
return {"message": "Welcome to the Book Collection API", "total_books": len(books)}
Your data models are locked in and your API already knows what a book looks like. Next up, you'll build endpoints that let users browse and search this collection.
Build the Read Endpoints
You have a Book model, sample data, and a running server. Now it is time to let users actually retrieve that data through dedicated API endpoints.
In this step, you will build two GET endpoints. One returns the full collection (with optional filtering). The other returns a single book by its ID.
In this step, get ready to:
- Build a GET endpoint that returns your full book collection with optional filtering.
- Build a GET endpoint that returns a single book by its ID.
- Test both endpoints in the interactive Swagger UI.
Create the GET /books endpoint
The first read endpoint returns the full list of books. To make it more useful, you will add optional query parameters that let callers filter by author or reading status.
- In main.py, scroll to the bottom of the file (below the read_root function).
- Add the following endpoint:
@app.get("/books")
def get_books(author: str | None = None, status: str | None = None):
# Start with the full book list
result = books
# Filter by author name (case-insensitive partial match)
if author:
result = [b for b in result if author.lower() in b["author"].lower()]
# Filter by reading status (exact match)
if status:
result = [b for b in result if b["status"] == status]
return result
What does this code do?
The function parameters author: str | None = None and status: str | None = None tell FastAPI these are optional query parameters. Callers can add ?author=orwell or ?status=unread to the URL to narrow results.
If no parameters are provided, the endpoint returns every book in the collection. When a filter is present, a list comprehension narrows the results before returning them.
- Save main.py.
- Switch to your browser and visit http://127.0.0.1:8000/books. You should see a JSON array containing all three sample books.
Seeing an empty page or error?
Make sure the new function is placed outside and below the read_root function. Python will raise a syntax error if the decorator is indented inside another function.
Check that your dev server is still running. If you stopped it, run fastapi dev main.py again in your terminal.
help me debug why my /books endpoint is not returning data
Create the GET /books/{book_id} endpoint
The second read endpoint returns a single book by its ID. This uses a path parameter so the ID is embedded directly in the URL. You also need proper error handling for when someone requests a book that does not exist.
- In main.py, find this import line at the top of the file:
from fastapi import FastAPI
- Replace it with this line to add HTTPException to the import:
from fastapi import FastAPI, HTTPException
What is HTTPException?
HTTPException is FastAPI's built-in way to return error responses with proper HTTP status codes. When you raise it, FastAPI stops processing and immediately sends back the status code and error detail you specify.
- Scroll to the bottom of main.py (below your get_books function) and add this endpoint:
@app.get("/books/{book_id}")
def get_book(book_id: int):
# Search for a book matching the given ID
for book in books:
if book["id"] == book_id:
return book
# No match found - return a 404 error
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
What does this code do?
The {book_id} in the path tells FastAPI to capture that URL segment and pass it to the function. Declaring book_id: int means FastAPI automatically validates that the value is an integer before your code even runs.
If no book matches the ID, raise HTTPException(status_code=404, ...) returns a 404 Not Found response with a descriptive error message.
- Save main.py.
- Visit http://127.0.0.1:8000/books/1 in your browser. You should see the JSON for "The Great Gatsby".
- Now try http://127.0.0.1:8000/books/99. You should see a JSON error response with "detail": "Book with id 99 not found".
Getting a 422 error instead of 404?
A 422 response means the path value failed type validation. Make sure you are passing a number in the URL (like /books/1) and not a word.
If you see a 500 Internal Server Error, check that you added HTTPException to your import line at the top of the file.
help me fix the error I am getting on my /books/{book_id} endpoint
Test both endpoints in Swagger UI
The browser URL bar works for quick checks, but Swagger UI gives you an interactive form for every endpoint. It is especially useful for testing query parameters without manually building URLs.
- Open http://127.0.0.1:8000/docs in your browser.
- Expand the GET /books section and click Try it out.
- Type orwell in the author field and click Execute. You should see only "1984" in the response.
- Clear the author field, type unread in the status field, and click Execute again. You should see only "To Kill a Mockingbird".
- Now expand the GET /books/{book_id} section, click Try it out, enter 99 as the book_id, and click Execute. You should see the 404 error response.
Why is automatic validation so powerful here?
You never wrote a single if statement to check whether book_id is an integer. FastAPI reads the type hint and rejects invalid input before your function runs. This means fewer bugs and less boilerplate code.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py and your server is still running.
ⓧ I'd like to double check the full code
Here is the complete main.py so far:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="Book Collection API")
class Book(BaseModel):
title: str
author: str
year: int
status: str = "unread"
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
year: int | None = None
status: str | None = None
books = [
{"id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "status": "read"},
{"id": 2, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960, "status": "unread"},
{"id": 3, "title": "1984", "author": "George Orwell", "year": 1949, "status": "reading"},
]
@app.get("/")
def read_root():
return {"message": "Welcome to the Book Collection API", "total_books": len(books)}
@app.get("/books")
def get_books(author: str | None = None, status: str | None = None):
result = books
if author:
result = [b for b in result if author.lower() in b["author"].lower()]
if status:
result = [b for b in result if b["status"] == status]
return result
@app.get("/books/{book_id}")
def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
Your API can now serve data to any client that asks for it. Next up, you will build the write endpoints that let users add, update, and delete books from the collection.
Build the Write Endpoints
Your API can read books, but a real collection needs to grow and change. Right now there is no way to add a new book, update your reading progress, or remove a title you no longer want.
In this step you will build the three write endpoints that complete the CRUD cycle: Create, Update, and Delete. Each one uses the Pydantic models you already defined to validate incoming data automatically.
In this step, get ready to:
- Create a POST endpoint that adds new books to your collection.
- Create a PUT endpoint that updates specific fields on an existing book.
- Create a DELETE endpoint that removes a book and test the full CRUD cycle.
Create the POST endpoint for adding books
A POST request sends data to your server to create a new resource. FastAPI automatically parses the request body into your Pydantic model and validates every field before your function runs.
- In main.py, scroll to the bottom of the file (below the get_book function).
- Add the POST endpoint by copying the following code at the end of the file:
@app.post("/books", status_code=201)
def create_book(book: Book):
# Generate the next available ID
new_id = max(b["id"] for b in books) + 1 if books else 1
# Convert the Pydantic model to a dict and add the ID
new_book = {"id": new_id, **book.model_dump()}
books.append(new_book)
return new_book
What does this code do?
The book: Book parameter tells FastAPI to read the request body and validate it against your Book model. If the data is invalid, FastAPI returns a 422 error automatically.
The status_code=201 tells clients that a new resource was created (instead of the default 200 "OK"). The model_dump() method converts the Pydantic model into a plain dictionary so you can store it in your list.
- Save main.py.
- Switch back to the Swagger UI at http://127.0.0.1:8000/docs and refresh the page. You should see a new green POST /books row in the endpoint list.
- Click POST /books, then click Try it out.
- Replace the example JSON body with this:
{
"title": "Dune",
"author": "Frank Herbert",
"year": 1965
}
- Click Execute. You should see a 201 response with the new book including "id": 4 and "status": "unread" (the default value from your model).
Don't see the POST endpoint in Swagger UI?
Make sure you saved main.py. The dev server auto-reloads on save, but unsaved changes won't appear.
Check that the new code is outside and below the get_book function, not indented inside it.
help me debug why my POST endpoint isn't showing up in Swagger UI
Create the PUT endpoint for updating books
A PUT endpoint lets clients change existing data. Your BookUpdate model makes every field optional, so a client can update just the status without re-sending the title, author, and year.
- In main.py, add the PUT endpoint below the create_book function you just wrote:
@app.put("/books/{book_id}")
def update_book(book_id: int, book_update: BookUpdate):
for book in books:
if book["id"] == book_id:
# Only include fields the client actually sent
update_data = book_update.model_dump(exclude_unset=True)
book.update(update_data)
return book
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
What does model_dump(exclude_unset=True) do?
This is the key to partial updates. Without exclude_unset=True, every field in BookUpdate would be included as None, overwriting your existing data.
With exclude_unset=True, only fields the client explicitly sent in the request body are included. So if someone sends just {"status": "reading"}, only the status gets updated. The title, author, and year remain untouched.
- Save main.py.
- Refresh Swagger UI. You should see a new blue PUT /books/{book_id} row.
- Click PUT /books/{book_id}, then click Try it out.
- Enter 2 for the book_id parameter.
- Replace the request body with:
{
"status": "reading"
}
- Click Execute. The response should show "To Kill a Mockingbird" with its status changed to "reading" while all other fields remain unchanged.
Seeing a 422 validation error?
Make sure your JSON body is valid. Common issues include missing quotes around keys, a trailing comma after the last field, or curly braces that aren't balanced.
help me understand the 422 error from my PUT endpoint
Create the DELETE endpoint and test the full CRUD cycle
The DELETE endpoint removes a book from your collection entirely. After adding this, you will have all four CRUD operations working together.
- In main.py, add the DELETE endpoint below the update_book function:
@app.delete("/books/{book_id}")
def delete_book(book_id: int):
for i, book in enumerate(books):
if book["id"] == book_id:
# Remove the book from the list
books.pop(i)
return {"message": f"Book with id {book_id} deleted"}
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
What does this code do?
The enumerate(books) gives you both the index (i) and the book dictionary at that position. You need the index to call books.pop(i), which removes the item from the list at that exact position.
If no book matches the given ID, the loop finishes without returning, and the HTTPException on the last line sends a 404 response.
- Save main.py.
- Refresh Swagger UI. You should now see a red DELETE /books/{book_id} row.
Now test the full CRUD cycle to see all your endpoints working together.
- Use POST /books to create a new book with any title, author, and year you like.
- Use GET /books to confirm your new book appears in the list.
- Use PUT /books/{book_id} with the new book's ID to change its status to "reading".
- Use DELETE /books/{book_id} with the same ID to remove it.
- Use GET /books one more time to confirm the book is gone.
Getting a 404 when deleting?
Double-check the ID you are sending matches the ID from the POST response. If you restarted the server, IDs reset because the data lives in memory only.
help me figure out why my DELETE endpoint returns 404
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete main.py at this point:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="Book Collection API")
class Book(BaseModel):
title: str
author: str
year: int
status: str = "unread"
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
year: int | None = None
status: str | None = None
books = [
{"id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "status": "read"},
{"id": 2, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960, "status": "unread"},
{"id": 3, "title": "1984", "author": "George Orwell", "year": 1949, "status": "reading"},
]
@app.get("/")
def read_root():
return {"message": "Welcome to the Book Collection API", "total_books": len(books)}
@app.get("/books")
def get_books(author: str | None = None, status: str | None = None):
result = books
if author:
result = [b for b in result if author.lower() in b["author"].lower()]
if status:
result = [b for b in result if b["status"] == status]
return result
@app.get("/books/{book_id}")
def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
@app.post("/books", status_code=201)
def create_book(book: Book):
new_id = max(b["id"] for b in books) + 1 if books else 1
new_book = {"id": new_id, **book.model_dump()}
books.append(new_book)
return new_book
@app.put("/books/{book_id}")
def update_book(book_id: int, book_update: BookUpdate):
for book in books:
if book["id"] == book_id:
update_data = book_update.model_dump(exclude_unset=True)
book.update(update_data)
return book
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
@app.delete("/books/{book_id}")
def delete_book(book_id: int):
for i, book in enumerate(books):
if book["id"] == book_id:
books.pop(i)
return {"message": f"Book with id {book_id} deleted"}
raise HTTPException(status_code=404, detail=f"Book with id {book_id} not found")
Your API now supports the full CRUD lifecycle. You can create books, browse and filter them, update their details, and remove them. Next up, you will extend this foundation with a rating system in the Secret Mission.
Secret mission
Add a Rating System
Your book API handles CRUD, but what about opinions? Build a rating feature that lets users score books from 1 to 5 and discover their top-rated reads with a dedicated endpoint.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files for future reference, stop the running server, or delete everything entirely.
Resources you used:
- The book-api project folder on your Desktop (includes your virtual environment and all installed packages).
- The FastAPI development server (running process in your terminal).
Keep everything running
No action needed. Choose this if you're still actively building or want to experiment with your API.
- Your FastAPI server keeps running at http://127.0.0.1:8000 and you can continue testing endpoints.
- Your book-api folder stays on your Desktop with all code and packages intact.
- Python and any globally installed tools remain on your machine.
Pause - I'll come back to this later
Shut down the running server to free up your terminal. Your project files stay on disk so you can pick up where you left off.
- Switch to the terminal where your FastAPI server is running.
- Press Ctrl+C to stop the server.
- Deactivate your virtual environment by running:
deactivate
To resume later, navigate back to your book-api folder, reactivate the virtual environment, and start the server again.
cd ~/Desktop/book-api
source venv/bin/activate
fastapi dev main.py
Delete - I don't want to use this again
Remove all project files and start fresh. This deletes your code, virtual environment, and all installed packages.
- Switch to the terminal where your FastAPI server is running.
- Press Ctrl+C to stop the server.
- Deactivate your virtual environment by running:
deactivate
- Delete the entire project folder by running:
macOS
cd ~/Desktop
rm -rf book-api
Windows
cd ~\Desktop
rmdir /s /q book-api
- Confirm the folder is gone by running ls (macOS) or dir (Windows) on your Desktop. You should no longer see book-api in the output.
Nice Work!
Nice Work!
You just built a complete REST API from scratch using FastAPI and Python. Your Book Collection API handles creating, reading, updating, and deleting books with automatic data validation and interactive documentation.
Here's what you learned:
- Built a FastAPI application with 5 CRUD endpoints that handle GET, POST, PUT, and DELETE requests for managing a book collection.
- Used Pydantic models to automatically validate request data without writing a single if statement.
- Explored auto-generated Swagger UI documentation at /docs to test every endpoint directly in the browser.
- Added a rating system with validated scores and a sorted "top rated" endpoint.
Ready to quiz yourself?