Build a Bookshelf API with FastAPI
Build a REST API with full CRUD operations using Python and FastAPI.
Introduction
30 Second Summary
Think about your personal book collection. You have favourites you recommend constantly, new additions you want to track, and titles you no longer need. Now imagine giving that collection an interface anyone can interact with programmatically.
In this project, you will build a REST API that manages a bookshelf of titles. You will add, browse, update, and remove books through HTTP endpoints, all powered by Python and FastAPI.
What You'll Build
A personal bookshelf REST API that performs full CRUD operations, validates incoming data automatically, and generates interactive documentation without any extra configuration.
By the end of this project, you'll have:
- A running FastAPI application with endpoints for creating, reading, updating, and deleting books.
- Validated request and response models using Pydantic that reject malformed data automatically.
- Auto-generated interactive API documentation (Swagger UI) accessible at /docs.
- Secret Mission: Add filtering and search to your bookshelf so you can find books by author or genre using query parameters.
Are there any prerequisites?
This project assumes you have programming experience in another language like Java or Node.js. No prior Python experience is required. If you understand HTTP methods, classes, and functions, you are ready to go.
Set Up Your Python Environment
Before you can build an API, you need an isolated workspace where your project's dependencies live separately from the rest of your system.
In this step, you'll create a project folder, set up a Python virtual environment (the equivalent of node_modules isolation in Node.js), install FastAPI, and confirm everything works by running a hello world server.
In this step, get ready to:
- Create a project directory and activate a virtual environment.
- Install FastAPI and verify the installation.
- Run a minimal FastAPI server and see auto-generated API docs in your browser.
Verify your Python version and create a virtual environment
FastAPI requires Python 3.10 or higher.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Confirm your Python version by running this command:
python3 --version
You should see a version number of 3.10 or higher (e.g. Python 3.12.4).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir bookshelf-api
cd bookshelf-api
- Confirm the folder exists by running:
ls ~/Desktop
You should see bookshelf-api listed in the output.
A virtual environment keeps your project's packages separate from other Python projects on your machine.
- Create a virtual environment by running this command:
python3 -m venv .venv
- Activate the virtual environment by running this command:
source .venv/bin/activate
Your terminal prompt should now show (.venv) at the beginning.
What is a virtual environment?
Think of it like a dedicated node_modules folder for a Node.js project. Packages you install while the environment is active only exist inside .venv, not system-wide.
The (.venv) prefix in your prompt confirms you're working inside the isolated environment.
Not seeing (.venv) in your prompt?
Make sure you ran the source command with the correct path. Check for typos in .venv/bin/activate.
If you're on Windows, the activation command is different. Use .venv\Scripts\activate instead.
Help me activate my virtual environment
Install FastAPI and verify
- Install FastAPI and its dependencies by running this command:
pip install "fastapi[standard]"
What does fastapi[standard] include?
The [standard] extra installs everything you need for development: the web server (Uvicorn), data validation (Pydantic), and the FastAPI CLI tool.
- Verify the installation succeeded by running:
python -c "import fastapi; print(fastapi.__version__)"
You should see a version number printed (e.g. 0.137.1).
Seeing a ModuleNotFoundError?
Make sure your virtual environment is still active. Look for (.venv) in your terminal prompt.
If it disappeared, re-activate with source .venv/bin/activate and then re-run the install command.
Help me fix a FastAPI import error
- Open your project in VS Code by running:
code .
VS Code opens with the bookshelf-api folder loaded in the sidebar.
Create the project file and run a hello world
Now you'll write a minimal FastAPI app to confirm the entire stack works end to end.
- In VS Code, click File in the top menu bar.
- Click New File.
- Name the file main.py and save it in the bookshelf-api folder.
- Add the following code to main.py:
from fastapi import FastAPI
# Create the FastAPI application instance
app = FastAPI(title="Bookshelf API")
# Root endpoint to verify the server is running
@app.get("/")
def root():
return {"message": "Welcome to the Bookshelf API"}
What does this code do?
- FastAPI(title="Bookshelf API") creates your application instance. The title shows up in the auto-generated docs.
- @app.get("/") is a decorator that registers the function below it as the handler for GET requests to the root URL.
- The function returns a Python dictionary, which FastAPI automatically converts to a JSON response.
- Save main.py.
- Switch back to your Terminal and start the development server by running:
fastapi dev
You should see output showing that Uvicorn is running on http://127.0.0.1:8000.
- Open your browser and go to http://127.0.0.1:8000.
You should see the JSON response: {"message": "Welcome to the Bookshelf API"}.
- Now open http://127.0.0.1:8000/docs in your browser.
You should see the Swagger UI page with your single endpoint listed.
What is Swagger UI?
FastAPI automatically generates interactive API documentation from your code. You get a visual interface where you can see every endpoint, its expected inputs, and even test it live.
No extra configuration needed. Every endpoint you add will appear here automatically.
Browser shows an error or nothing loads?
Make sure your terminal still shows the server running. If you see an error instead of the Uvicorn startup message, check that your main.py file is saved and that you're running fastapi dev from inside the bookshelf-api folder.
Also confirm your virtual environment is still active (you should see (.venv) in the prompt).
Help me get my FastAPI server running
✔️ Awesome, I've got everything!
Great. Make sure you've 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
app = FastAPI(title="Bookshelf API")
@app.get("/")
def root():
return {"message": "Welcome to the Bookshelf API"}
Your environment is ready and your server is running. Next up, you'll define the data model for your books using Pydantic.
Define Your Book Model with Pydantic
Your API is running and returning a simple welcome message. Now you need to define what a "book" actually looks like so the API can validate incoming data automatically.
In this step, you will create Pydantic models that describe the shape of a book. These models act as a contract between your API and anyone who calls it. If someone sends malformed data, FastAPI rejects it before your code even runs.
In this step, get ready to:
- Define Pydantic models for creating and reading books.
- Create an in-memory data store with sample books.
- Wire up a GET endpoint that returns all books.
Create the Book data models
Every API needs to know what shape of data it accepts. In Java, you would write a DTO class with annotations. In Node.js, you might use Zod or Joi schemas. In Python with FastAPI, you declare a class that inherits from Pydantic's BaseModel and add type hints to each field.
- In main.py, find the line from fastapi import FastAPI at the top of the file.
- Add this new import directly below it:
from pydantic import BaseModel, Field
- Below the app = FastAPI(title="Bookshelf API") line, add these two model classes:
# --- Models ---
class BookCreate(BaseModel):
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
class Book(BaseModel):
id: int
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
What does this code do?
You are creating two models that define the shape of a book:
- BookCreate is what someone sends when adding a new book. It has title, author, genre, and rating. No id field here because the API assigns IDs automatically.
- Book is what the API returns. It includes the id field so the caller knows which book they are looking at.
- Field(ge=1, le=5) tells Pydantic that rating must be between 1 and 5. Any value outside that range gets rejected automatically.
You won't see a change in the browser yet. The next chunk uses these models in a real endpoint.
Create the in-memory data store
Your API needs somewhere to store books. For this project, you will use a simple Python list. This keeps the focus on API mechanics without introducing database setup.
- In main.py, add the following data store below your Book class (after the models section):
# --- In-memory data store ---
books: list[dict] = [
{"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "genre": "Fantasy", "rating": 5},
{"id": 2, "title": "1984", "author": "George Orwell", "genre": "Dystopian", "rating": 4},
{"id": 3, "title": "Dune", "author": "Frank Herbert", "genre": "Science Fiction", "rating": 5},
]
next_id: int = 4
What does this code do?
The books list holds dictionaries that each represent one book. Pre-populating it with three sample books means you will have data to see immediately when you test the API.
The next_id counter starts at 4 because your three sample books use IDs 1, 2, and 3. Every time you create a new book later, the API will use next_id and increment it.
You still won't see a change in the browser. The next chunk adds the endpoint that surfaces this data.
Wire up a GET endpoint that returns all books
Now you will connect your models and data store to a real endpoint so the API can serve your book collection.
- In main.py, find the existing @app.get("/") endpoint. Add a # --- Endpoints --- comment directly above it.
- Below the root() function, add this new endpoint:
@app.get("/books", response_model=list[Book])
def get_books():
return books
What does this code do?
The @app.get("/books") decorator creates a GET endpoint at the /books path. When someone visits that URL, FastAPI calls get_books() and returns the result as JSON.
The response_model=list[Book] parameter tells FastAPI to validate and serialize the response using your Book model. It also generates the schema automatically in the Swagger UI documentation.
- Save main.py. The dev server auto-reloads when you save.
- Refresh http://127.0.0.1:8000/docs in your browser. You should see the GET /books endpoint listed alongside your original GET / endpoint.
- Click the GET /books section to expand it, then click Try it out and Execute.
- You should see a JSON response containing your three sample books (The Hobbit, 1984, and Dune).
Not seeing the new endpoint?
- Check your terminal for error messages. A typo in the model definitions will prevent the server from reloading successfully.
- Make sure you saved main.py. The server only reloads when the file is written to disk.
- Verify that your from pydantic import BaseModel, Field line is on its own line directly below the FastAPI import.
Help me debug why my FastAPI endpoint is not showing up in Swagger UI.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py and the Swagger UI shows all three sample books in the response.
ⓧ I'd like to double check the full code
Here is what your complete main.py should look like at this point:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(title="Bookshelf API")
# --- Models ---
class BookCreate(BaseModel):
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
class Book(BaseModel):
id: int
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
# --- In-memory data store ---
books: list[dict] = [
{"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "genre": "Fantasy", "rating": 5},
{"id": 2, "title": "1984", "author": "George Orwell", "genre": "Dystopian", "rating": 4},
{"id": 3, "title": "Dune", "author": "Frank Herbert", "genre": "Science Fiction", "rating": 5},
]
next_id: int = 4
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookshelf API"}
@app.get("/books", response_model=list[Book])
def get_books():
return books
Your data models and sample bookshelf are ready. Next up, you will build the POST endpoint to add new books and the GET endpoint to retrieve a single book by its ID.
Build the Create and Read Endpoints
Your bookshelf already has a data model and an endpoint that lists every book. Now it's time to make the API interactive.
In this step, you'll add the ability to create new books and look up individual titles by their ID. These are the "C" and the single-item "R" in CRUD.
In this step, get ready to:
- Add a POST endpoint that creates new books.
- Add a GET endpoint that retrieves a single book by its ID.
- Test both endpoints in the interactive Swagger UI.
Add the POST /books endpoint
The POST endpoint is how new books get added to your collection. When a request comes in, the API validates the data, assigns a unique ID, and stores the book.
- In main.py, find the existing get_books() function and add the following code below it:
@app.post("/books", response_model=Book, status_code=201)
def create_book(book_data: BookCreate):
global next_id
# Build a full book dict by combining the auto-assigned ID with the request data
new_book = {"id": next_id, **book_data.model_dump()}
books.append(new_book)
next_id += 1
return new_book
What does this code do?
The @app.post decorator tells FastAPI this function handles POST requests to /books. The status_code=201 means the response signals "resource created" instead of the default 200.
The book_data: BookCreate parameter tells FastAPI to parse the request body as a BookCreate model. If the data doesn't match (wrong types, missing fields, rating outside 1-5), FastAPI rejects the request automatically.
The .model_dump() method converts the Pydantic model into a plain dictionary. The ** unpacks that dictionary so you can merge it with the id field into a single dict.
- Save main.py.
- Switch to your browser and refresh http://127.0.0.1:8000/docs. You should see a new green POST /books row in the endpoint list.
Don't see the new endpoint?
- Check your terminal. You should see a line like WatchFiles detected changes in 'main.py'. Reloading... confirming the server restarted. If not, make sure you saved the file.
- If you see a syntax error in the terminal, double-check the indentation. Python requires consistent spacing (4 spaces per level).
- Still stuck? Why isn't my POST /books endpoint showing in Swagger UI?
Add GET /books/{book_id} to fetch a single book
Listing all books is useful, but users also need to look up a specific book by its ID. This endpoint introduces path parameters and error handling for missing resources.
- In main.py, find the import line at the top of the file:
from fastapi import FastAPI
- Update it to also import HTTPException, so the line looks like this:
from fastapi import FastAPI, HTTPException
What is HTTPException?
HTTPException is FastAPI's built-in way to return error responses with the correct status code. Instead of returning a generic 200 with an error message, you raise an exception that FastAPI converts into a proper HTTP error response (like 404 Not Found).
- Now find the create_book() function you just added, and paste this code above it (between get_books() and create_book()):
@app.get("/books/{book_id}", response_model=Book)
def get_book(book_id: int):
# Search for the book by 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="Book not found")
What does this code do?
The {book_id} in the route is a path parameter. FastAPI reads the value from the URL and passes it to your function. The type annotation book_id: int tells FastAPI to convert it to an integer automatically.
If no book matches the given ID, the function raises HTTPException(status_code=404). This returns a JSON response like {"detail": "Book not found"} with the correct 404 status code.
- Save main.py.
- Refresh http://127.0.0.1:8000/docs. You should now see a blue GET /books/{book_id} row alongside your other endpoints.
Seeing an import error in the terminal?
- Make sure HTTPException is on the same line as FastAPI, separated by a comma. The full line should be from fastapi import FastAPI, HTTPException.
- Check that the function is indented correctly and sits at the same level as your other endpoint functions.
- Need more help? My FastAPI app crashes after adding HTTPException
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and both new endpoints appear in Swagger UI.
ⓧ 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, Field
app = FastAPI(title="Bookshelf API")
# --- Models ---
class BookCreate(BaseModel):
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
class Book(BaseModel):
id: int
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
# --- In-memory data store ---
books: list[dict] = [
{"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "genre": "Fantasy", "rating": 5},
{"id": 2, "title": "1984", "author": "George Orwell", "genre": "Dystopian", "rating": 4},
{"id": 3, "title": "Dune", "author": "Frank Herbert", "genre": "Science Fiction", "rating": 5},
]
next_id: int = 4
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookshelf API"}
@app.get("/books", response_model=list[Book])
def get_books():
return books
@app.get("/books/{book_id}", response_model=Book)
def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
raise HTTPException(status_code=404, detail="Book not found")
@app.post("/books", response_model=Book, status_code=201)
def create_book(book_data: BookCreate):
global next_id
new_book = {"id": next_id, **book_data.model_dump()}
books.append(new_book)
next_id += 1
return new_book
Test both endpoints in Swagger UI
With both endpoints in place, you can run a full create-and-retrieve cycle directly from your browser. Swagger UI's "Try it out" feature sends real HTTP requests to your running server.
- In your browser at http://127.0.0.1:8000/docs, click the green POST /books row to expand it.
- Click Try it out.
- Replace the example JSON in the request body with this:
{
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"genre": "Classic",
"rating": 4
}
- Click Execute.
- You should see a response with status 201 and the new book with "id": 4 assigned automatically.
Now test the GET-by-ID endpoint.
- Click the blue GET /books/{book_id} row to expand it.
- Click Try it out.
- Enter 4 in the book_id field and click Execute.
- You should see the full book object for The Great Gatsby returned with status 200.
Finally, test the error handling.
- In the same GET /books/{book_id} panel, change the book_id to 99 and click Execute.
- You should see a 404 response with {"detail": "Book not found"}.
Why proper error codes matter
Returning a 404 instead of a generic 200 with an error message is a key REST API best practice. It lets clients (frontends, mobile apps, other services) programmatically distinguish "success" from "not found" without parsing the response body.
Your API can now create books and retrieve them individually. Next up, you'll complete the CRUD cycle by adding update and delete endpoints.
Build the Update and Delete Endpoints
Your API can create and retrieve books. That covers half of the CRUD lifecycle.
In this step, you will add the remaining two operations: updating an existing book and deleting one. Once these are in place, your bookshelf API will be fully functional.
In this step, get ready to:
- Add a PUT endpoint that replaces a book's data.
- Add a DELETE endpoint that removes a book from the collection.
- Run a full CRUD lifecycle test in Swagger UI.
Add PUT /books/{book_id} to update a book
A PUT request replaces an existing resource with new data. The client sends the book's ID in the URL and the updated fields in the request body.
- In main.py, scroll to the bottom of your file and find the create_book function.
- Add the PUT endpoint below it by copying this code:
@app.put("/books/{book_id}", response_model=Book)
def update_book(book_id: int, book_data: BookCreate):
# Loop through books to find the matching ID
for i, book in enumerate(books):
if book["id"] == book_id:
# Replace the old book data with the new data
updated_book = {"id": book_id, **book_data.model_dump()}
books[i] = updated_book
return updated_book
# If no book matched, return a 404 error
raise HTTPException(status_code=404, detail="Book not found")
What does this code do?
The function accepts two inputs: book_id from the URL path and book_data from the request body. FastAPI validates the body against BookCreate automatically.
The enumerate(books) call gives you both the index and the book dictionary so you can replace it in the list. The model_dump() method converts the Pydantic model into a plain dictionary.
- Save main.py.
- Switch to your browser and refresh http://127.0.0.1:8000/docs. You should see a new green PUT /books/{book_id} entry in the endpoint list.
Don't see the PUT endpoint?
Make sure you saved main.py. The fastapi dev server reloads automatically on save, but unsaved changes will not appear.
Check that the new function is at the same indentation level as your other endpoint functions. If it is nested inside create_book, Python will not treat it as a separate function.
help me debug my PUT endpoint not showing up in Swagger UI
Add DELETE /books/{book_id} to remove a book
The DELETE endpoint removes a book from the collection entirely. It uses the same path parameter pattern as GET and PUT.
- In main.py, add the DELETE endpoint below your update_book function by copying this code:
@app.delete("/books/{book_id}")
def delete_book(book_id: int):
# Loop through books to find the matching ID
for i, book in enumerate(books):
if book["id"] == book_id:
# Remove the book from the list
books.pop(i)
return {"message": f"Book {book_id} deleted successfully"}
# If no book matched, return a 404 error
raise HTTPException(status_code=404, detail="Book not found")
What does this code do?
The books.pop(i) call removes the book at index i from the list. Once removed, the function returns a confirmation message with the book's ID.
Notice this endpoint does not have a response_model parameter. The response is a simple dictionary with a message string, so there is no need for a Pydantic model.
- Save main.py.
- Refresh http://127.0.0.1:8000/docs. You should now see a red DELETE /books/{book_id} entry alongside your other endpoints.
Seeing an error in the terminal?
If your terminal shows a syntax error after saving, check for a missing colon at the end of the def delete_book(book_id: int): line. Also confirm the f"Book {book_id} deleted successfully" string uses an f prefix before the opening quote.
help me fix a syntax error in my delete endpoint
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py before moving on to the CRUD test.
ⓧ I'd like to double check the full code
Here is the complete main.py with all endpoints:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="Bookshelf API")
# --- Models ---
class BookCreate(BaseModel):
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
class Book(BaseModel):
id: int
title: str
author: str
genre: str
rating: int = Field(ge=1, le=5)
# --- In-memory data store ---
books: list[dict] = [
{"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "genre": "Fantasy", "rating": 5},
{"id": 2, "title": "1984", "author": "George Orwell", "genre": "Dystopian", "rating": 4},
{"id": 3, "title": "Dune", "author": "Frank Herbert", "genre": "Science Fiction", "rating": 5},
]
next_id: int = 4
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookshelf API"}
@app.get("/books", response_model=list[Book])
def get_books():
return books
@app.get("/books/{book_id}", response_model=Book)
def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
raise HTTPException(status_code=404, detail="Book not found")
@app.post("/books", response_model=Book, status_code=201)
def create_book(book_data: BookCreate):
global next_id
new_book = {"id": next_id, **book_data.model_dump()}
books.append(new_book)
next_id += 1
return new_book
@app.put("/books/{book_id}", response_model=Book)
def update_book(book_id: int, book_data: BookCreate):
for i, book in enumerate(books):
if book["id"] == book_id:
updated_book = {"id": book_id, **book_data.model_dump()}
books[i] = updated_book
return updated_book
raise HTTPException(status_code=404, detail="Book 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 {book_id} deleted successfully"}
raise HTTPException(status_code=404, detail="Book not found")
Run the full CRUD test cycle in Swagger UI
Now that all four operations are in place, you can run the complete lifecycle of a book: create, read, update, verify, delete, and confirm deletion.
- In your browser, go to http://127.0.0.1:8000/docs.
- Expand the POST /books endpoint and click Try it out.
- Replace the example JSON body with this:
{
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"genre": "Classic",
"rating": 4
}
- Click Execute. Note the id in the response (it should be 4).
- Expand GET /books/{book_id}, click Try it out, enter 4 as the book_id, and click Execute. You should see The Great Gatsby returned.
- Expand PUT /books/{book_id}, click Try it out, and enter 4 as the book_id.
- Change the rating to 5 in the JSON body and click Execute. The response should show the updated rating.
- Use GET /books/{book_id} again with ID 4 to confirm the update persisted.
- Expand DELETE /books/{book_id}, click Try it out, enter 4, and click Execute. You should see a confirmation message.
- Finally, use GET /books/{book_id} with ID 4 one more time. You should receive a 404 response confirming the book was deleted.
Why test the full lifecycle?
Walking through Create, Read, Update, Read again, Delete, and Read again proves that every operation interacts correctly with the shared data store. A bug in your update logic might silently corrupt data that only surfaces when you try to read it back.
Getting a 404 when updating or deleting?
Make sure you are using the correct id from the POST response. If you restarted the server between tests, the in-memory data resets and previously created books no longer exist.
help me figure out why my PUT or DELETE returns 404 even though I just created the book
Secret mission
Add Filtering and Search
Your bookshelf API returns every single book every time. What if you have hundreds of books and only want to see fantasy novels? Add query parameters that let users filter by author, genre, or search across titles.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The bookshelf-api project directory on your Desktop.
- The .venv virtual environment inside the project folder.
- The FastAPI dev server (running process).
Keep everything running
No action needed. Choose this if you are still actively building or want to keep experimenting with your API.
- Your FastAPI dev server continues running in the terminal. You can test endpoints anytime at http://127.0.0.1:8000/docs.
- Your virtual environment stays activated as long as the terminal session is open.
- All project files remain in ~/Desktop/bookshelf-api.
Pause - I'll come back to this later
Shut down the running server to free up the terminal. Your files stay on disk so you can pick up later.
- In the terminal where FastAPI is running, press Ctrl+C to stop the dev server.
- Deactivate your virtual environment by running:
deactivate
When you come back, reactivate and restart by running:
cd ~/Desktop/bookshelf-api
source .venv/bin/activate
fastapi dev
Delete - I don't want to use this again
Remove all project files and start fresh. This deletes everything you built in this project.
- In the terminal where FastAPI is running, press Ctrl+C to stop the dev server.
- Deactivate the virtual environment by running:
deactivate
- Remove the entire project directory by running:
rm -rf ~/Desktop/bookshelf-api
- Confirm the folder is gone by running:
ls ~/Desktop
You should no longer see bookshelf-api in the output.
Nice Work!
Nice Work!
You just built a fully functional REST API from scratch using Python and FastAPI. Your bookshelf API handles creating, reading, updating, and deleting books with validated data and professional documentation.
You've learned how to:
- Build a complete REST API with full CRUD operations using FastAPI and Python.
- Validate incoming data automatically using Pydantic models with type hints and field constraints.
- Generate interactive Swagger UI documentation at /docs with zero extra configuration.
- Secret Mission: Added query parameter filtering and search so you can find books by author, genre, or keyword.
Ready to quiz yourself?