Build a Bookstore REST API with FastAPI
Build a full CRUD REST API for a bookstore inventory on your Android device.
Introduction
30 Second Summary
Every app on your phone talks to a server somewhere, sending requests and getting back data. But what does the other side of that conversation actually look like?
In this project, you will build a complete REST API from scratch on your Android device using Python and FastAPI. You will create endpoints that handle book inventory data, validate incoming requests, and return proper HTTP responses.
What You'll Build
A fully functional Bookstore Inventory REST API running locally on your Android device with five endpoints covering all CRUD operations.
By the end of this project, you'll have:
- A working REST API running on your phone that creates, reads, updates, and deletes books from an in-memory data store with proper HTTP status codes.
- Hands-on experience with Pydantic data validation, seeing exactly how invalid requests get rejected with clear error messages.
- An understanding of auto-generated Swagger UI documentation from the server-side perspective your mobile apps consume.
- Secret Mission: Add search and filtering with query parameters so API consumers can find books by author or price range.
Are there any prerequisites?
You should be comfortable writing Python (functions, dictionaries, classes). A basic understanding of HTTP concepts like GET and POST requests from your mobile development experience is helpful but not required.
Set Up FastAPI in Pydroid 3
As a mobile developer, you have consumed dozens of REST APIs from the client side. Now you will flip perspectives and build the server that handles those requests.
You already have FastAPI and uvicorn installed in Pydroid 3. Time to write your first endpoint and see it respond to HTTP requests right on your phone.
In this step, get ready to:
- Create main.py with a minimal FastAPI application and a root endpoint.
- Run the server and verify your API responds in the browser.
- Explore the auto-generated Swagger UI documentation at /docs.
Create your FastAPI application
Your API starts with a single Python file that defines the application instance and its first endpoint.
- In Pydroid 3, tap the folder icon at the top-right corner of the editor.
- Select New to open a blank file in the editor.
- Paste the following code into the editor:
from fastapi import FastAPI
app = FastAPI(title="Bookstore Inventory API", version="1.0.0")
@app.get("/")
def root():
return {"message": "Welcome to the Bookstore Inventory API"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
What does this code do?
- from fastapi import FastAPI imports the framework that handles routing and request validation for you.
- app = FastAPI(...) creates your application instance. The title and version values appear in the auto-generated documentation page.
- @app.get("/") registers the root() function as the handler for GET requests to /. It returns a Python dictionary that FastAPI converts to JSON automatically.
- uvicorn.run(app, host="127.0.0.1", port=8000) starts the server when you tap Run. It listens only on your device's local address so nothing is exposed to the network.
- Tap the folder icon at the top-right corner again.
- Select Save and choose a destination folder on your device (internal storage works fine).
- Enter main.py as the file name and tap SAVE.
File not saving?
- Make sure you granted Pydroid 3 storage permissions when prompted. Without them, the app cannot write files to your device.
- If you do not see the folder icon, look for a three-dot menu at the top of the editor and find file options there.
Help me save a file in Pydroid 3 on my Android device.
Run the server and test in your browser
Pydroid 3 runs your script with a single tap. The server stays alive until you stop it manually.
- Tap the yellow play button at the bottom of the Pydroid 3 editor to run your script.
A terminal panel appears at the bottom of the screen. You should see output that includes Uvicorn running on http://127.0.0.1:8000. This means your server is live.
- Switch to your phone's browser (Chrome or any browser app).
- Type http://127.0.0.1:8000 in the address bar and tap Go.
You should see a JSON response: {"message":"Welcome to the Bookstore Inventory API"}.
- Now navigate to http://127.0.0.1:8000/docs in the same browser.
You should see the Swagger UI page with your API title and version displayed at the top, along with your root endpoint listed.
What is Swagger UI?
Swagger UI is an interactive documentation page that lets you explore and test every endpoint in your API directly from the browser. FastAPI generates it from your code with zero extra configuration.
As a mobile developer, you have likely consumed APIs that provide this kind of documentation. Now you can see how it is generated on the server side.
Not seeing the server output?
- Make sure you saved the file as main.py before tapping the play button. Unsaved changes will not execute.
- Check for typos in 127.0.0.1 and 8000. A missing digit causes a bind error.
- If you see an address-already-in-use error, a previous run may still be active. Tap the stop button in the terminal panel, then tap the play button again.
Help me debug why my FastAPI server is not starting in Pydroid 3.
✔️ Awesome, I've got everything!
Great. Double check you saved main.py and that the server is still running before moving on.
ⓧ I'd like to double check the full code
from fastapi import FastAPI
app = FastAPI(title="Bookstore Inventory API", version="1.0.0")
@app.get("/")
def root():
return {"message": "Welcome to the Bookstore Inventory API"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Your API is live and responding to requests. Next up, you will define a data model that validates incoming book data before it ever reaches your endpoint logic.
Define the Book Data Model
Your server is up and responding to requests. Now you need to tell it what shape the data should take when clients send book information.
In this step, you will define Pydantic models that describe a book's structure. You will also seed the API with sample data and expose it through a new endpoint.
In this step, get ready to:
- Define the Book and BookUpdate data models with Pydantic.
- Set up an in-memory data store pre-seeded with sample books.
- Add a GET /books endpoint and test it in Swagger UI.
Define the Book and BookUpdate models
Every API needs to define what valid data looks like. FastAPI uses Pydantic models for this. A Pydantic model is a Python class that declares what fields a piece of data must have and what types those fields are.
- If the server from the previous step is still running, stop it by tapping the stop button in Pydroid 3's run controls.
- Switch back to your main.py file in the editor.
- Find the line from fastapi import FastAPI at the top. Add the Pydantic import directly below it, then add the Book model below the app = FastAPI(...) line by copying this code:
from pydantic import BaseModel
app = FastAPI(title="Bookstore Inventory API", version="1.0.0")
# --- Data Models ---
class Book(BaseModel):
title: str
author: str
price: float
isbn: str
in_stock: bool = True
What does this code do?
BaseModel is the parent class from Pydantic that gives your model automatic validation powers. Any class that inherits from it gets type checking for free.
The Book class declares five fields. If a client sends a request missing title or passes a string where price expects a float, FastAPI will automatically reject the request with a clear error message.
- Directly below the Book class, add the BookUpdate model by copying this code:
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
price: float | None = None
in_stock: bool | None = None
Why are all fields optional here?
BookUpdate is for partial updates. When a client wants to change only a book's price, they should not be forced to resend the title, author, and every other field.
The str | None = None syntax means the field can be a string or absent entirely. You will use this model in Step 4 when you build the update endpoint.
You will not see a change yet. The next chunk adds data for these models to hold.
Create the in-memory data store
Your models define the shape of a book. Now you need somewhere to store actual book data. A Python dictionary works perfectly as a lightweight in-memory database for this project.
- In main.py, add the following data store below the BookUpdate class and above the existing @app.get("/") endpoint by copying this code:
# --- In-Memory Data Store ---
books_db: dict[str, Book] = {
"978-0-13-468599-1": Book(
title="The Pragmatic Programmer",
author="David Thomas",
price=49.99,
isbn="978-0-13-468599-1",
),
"978-0-596-51774-8": Book(
title="JavaScript: The Good Parts",
author="Douglas Crockford",
price=29.99,
isbn="978-0-596-51774-8",
),
"978-1-49-195016-0": Book(
title="Fluent Python",
author="Luciano Ramalho",
price=59.99,
isbn="978-1-49-195016-0",
),
}
What does this code do?
books_db is a dictionary that maps ISBN strings to Book objects. Using the ISBN as the key makes it fast to look up, update, or delete a specific book.
Pre-seeding with three sample books means you will have data to query immediately when you test the API. No need to manually create books before you can see results.
You will not see a change yet. The next chunk adds an endpoint that returns this data.
Add the GET /books endpoint
With the data store in place, you can now expose it through an endpoint. This will let any client request the full list of books.
- In main.py, find the existing @app.get("/") endpoint. Add a section comment above it, then add the new endpoint below the root() function and above the if __name__ block by copying this code:
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookstore Inventory API"}
@app.get("/books", response_model=list[Book])
def get_all_books():
return list(books_db.values())
What does this code do?
response_model=list[Book] tells FastAPI that this endpoint returns a list of Book objects. FastAPI uses this to validate the response and generate accurate documentation in Swagger UI.
books_db.values() returns all the Book objects from the dictionary. Wrapping it in list() converts it to a plain list that FastAPI can serialize to JSON.
Replace the old root endpoint
You are replacing the old @app.get("/") block with the version above (which includes the # --- Endpoints --- comment). Make sure you do not end up with two copies of the root endpoint.
- Save main.py.
- Tap **Run** in Pydroid 3 to restart the server.
- Open your phone's browser and navigate to http://127.0.0.1:8000/docs to open the Swagger UI.
- Find the GET /books endpoint in the list. Tap it to expand it.
- Tap **Try it out**, then tap **Execute**.
- You should see a JSON array containing three books (The Pragmatic Programmer, JavaScript: The Good Parts, and Fluent Python) in the response body.
Not seeing the /books endpoint in Swagger UI?
- Make sure the server restarted cleanly. Check the Pydroid 3 terminal output for errors. A SyntaxError or IndentationError means something is misaligned in your code.
- Confirm that the from pydantic import BaseModel line is at the top of the file, directly below from fastapi import FastAPI.
- Make sure the get_all_books function is above the if __name__ == "__main__" block. Code after that block does not execute when you tap Run.
Why isn't my /books endpoint appearing in Swagger UI after restarting the server?
✔️ 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. Compare it against yours line by line.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Bookstore Inventory API", version="1.0.0")
# --- Data Models ---
class Book(BaseModel):
title: str
author: str
price: float
isbn: str
in_stock: bool = True
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
price: float | None = None
in_stock: bool | None = None
# --- In-Memory Data Store ---
books_db: dict[str, Book] = {
"978-0-13-468599-1": Book(
title="The Pragmatic Programmer",
author="David Thomas",
price=49.99,
isbn="978-0-13-468599-1",
),
"978-0-596-51774-8": Book(
title="JavaScript: The Good Parts",
author="Douglas Crockford",
price=29.99,
isbn="978-0-596-51774-8",
),
"978-1-49-195016-0": Book(
title="Fluent Python",
author="Luciano Ramalho",
price=59.99,
isbn="978-1-49-195016-0",
),
}
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookstore Inventory API"}
@app.get("/books", response_model=list[Book])
def get_all_books():
return list(books_db.values())
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Your API can now serve book data. Next up, you will build endpoints that let clients create new books and look up individual titles by ISBN.
Build Create and Read Endpoints
Your API can list all books in the store. That is a great start, but a real API needs to let clients add new books and look up specific ones.
In this step, you will add a POST endpoint for creating books and a GET endpoint that retrieves a single book by its ISBN. Both endpoints will include proper error handling so clients get clear feedback when something goes wrong.
In this step, get ready to:
- Add a POST endpoint that creates new books with duplicate detection.
- Add a GET endpoint that retrieves a single book by ISBN.
- Test success and error cases in Swagger UI.
Add the POST /books endpoint
The POST endpoint lets clients send a new book as JSON in the request body. FastAPI automatically parses and validates that JSON against your Book model before your function even runs.
- In main.py, find the import line at the very top of the file:
from fastapi import FastAPI
- Add HTTPException to the import so the line now looks like this:
from fastapi import FastAPI, HTTPException
What is HTTPException?
HTTPException is FastAPI's built-in way to return error responses with proper status codes. When you raise it, FastAPI stops processing the request and sends back the error code and message you specify.
- Find the get_all_books function. Add the following POST endpoint below it (above the if __name__ block):
@app.post("/books", response_model=Book, status_code=201)
def create_book(book: Book):
if book.isbn in books_db:
raise HTTPException(status_code=409, detail="A book with this ISBN already exists")
books_db[book.isbn] = book
return book
What does this code do?
status_code=201 tells FastAPI to return HTTP 201 (Created) instead of the default 200. This is the correct status code when a new resource is successfully created.
The book: Book parameter tells FastAPI to parse the request body as JSON and validate it against your Book model. If any required field is missing or has the wrong type, FastAPI automatically rejects the request with a 422 error before your code runs.
The if book.isbn in books_db check prevents duplicate entries. If someone tries to add a book with an ISBN that already exists, the endpoint returns a 409 (Conflict) error.
- Save main.py.
Seeing a syntax error?
Make sure the @app.post decorator is on its own line directly above def create_book. Also check that the import line at the top now includes HTTPException after FastAPI.
help me fix a syntax error in my FastAPI POST endpoint
Add the GET /books/{isbn} endpoint
A path parameter lets the client request a specific resource by including its identifier directly in the URL. FastAPI extracts the value from the URL and passes it to your function automatically.
- In main.py, add the following endpoint below the create_book function (still above the if __name__ block):
@app.get("/books/{isbn}", response_model=Book)
def get_book(isbn: str):
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
return books_db[isbn]
What does this code do?
The {isbn} in the route path is a path parameter. When a client requests /books/978-0-13-468599-1, FastAPI extracts that ISBN string and passes it to the isbn parameter of your function.
If the ISBN does not exist in books_db, the endpoint raises a 404 (Not Found) error with a clear message. This is the standard HTTP status code for requesting a resource that does not exist.
- Save main.py.
Endpoint not showing up in Swagger?
Make sure the new endpoint is placed before the if __name__ == "__main__" block. Any code after that block only runs when the script starts, so FastAPI never registers the route.
help me figure out why my GET /books/{isbn} endpoint isn't appearing
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete main.py at this point. Compare it against yours:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="Bookstore Inventory API", version="1.0.0")
# --- Data Models ---
class Book(BaseModel):
title: str
author: str
price: float
isbn: str
in_stock: bool = True
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
price: float | None = None
in_stock: bool | None = None
# --- In-Memory Data Store ---
books_db: dict[str, Book] = {
"978-0-13-468599-1": Book(
title="The Pragmatic Programmer",
author="David Thomas",
price=49.99,
isbn="978-0-13-468599-1",
),
"978-0-596-51774-8": Book(
title="JavaScript: The Good Parts",
author="Douglas Crockford",
price=29.99,
isbn="978-0-596-51774-8",
),
"978-1-49-195016-0": Book(
title="Fluent Python",
author="Luciano Ramalho",
price=59.99,
isbn="978-1-49-195016-0",
),
}
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookstore Inventory API"}
@app.get("/books", response_model=list[Book])
def get_all_books():
return list(books_db.values())
@app.post("/books", response_model=Book, status_code=201)
def create_book(book: Book):
if book.isbn in books_db:
raise HTTPException(status_code=409, detail="A book with this ISBN already exists")
books_db[book.isbn] = book
return book
@app.get("/books/{isbn}", response_model=Book)
def get_book(isbn: str):
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
return books_db[isbn]
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Test both endpoints in Swagger UI
Now you will run the full test cycle: create a new book, retrieve it, and trigger each error case to see how your API responds.
- Stop the currently running server by tapping the stop button in Pydroid 3.
- Tap **Run** to restart the server with your new endpoints.
- Open your browser and navigate to http://127.0.0.1:8000/docs.
You should see your two new endpoints listed: POST /books and GET /books/{isbn}.
- Expand the POST /books section and tap **Try it out**.
- Replace the example JSON body with the following:
{
"title": "Clean Code",
"author": "Robert C. Martin",
"price": 39.99,
"isbn": "978-0-13-235088-4",
"in_stock": true
}
- Tap **Execute**.
You should see a response with status code 201 and the book data returned as JSON.
- Now expand the GET /books/{isbn} section and tap **Try it out**.
- Enter 978-0-13-235088-4 in the isbn field.
- Tap **Execute**.
You should see the book you just created returned as JSON with a 200 status code.
What about the error cases?
Try these to see your error handling in action:
- POST the same book again (same ISBN). You should get a 409 response with the message "A book with this ISBN already exists".
- GET a non-existent ISBN like 000-0-00-000000-0. You should get a 404 response with "Book not found".
- POST a book with a missing required field (delete the title field from the JSON). Pydantic automatically rejects it with a 422 response explaining exactly which field is missing.
Getting a 500 Internal Server Error?
Check the Pydroid 3 terminal for the full error traceback. A common cause is forgetting to add HTTPException to the import line at the top of main.py.
help me fix a 500 error in my FastAPI bookstore API
Your API can now create and retrieve individual books with proper error handling. Next up, you will complete the full CRUD cycle by adding update and delete endpoints.
Build Update and Delete Endpoints
Your API can create and retrieve books. To complete the CRUD lifecycle, you need two more operations: updating existing records and deleting them.
Partial updates are trickier than they sound because you need to tell the difference between a field the client wants to change and one they simply didn't include. Pydantic has a built-in solution for this.
In this step, get ready to:
- Add a PUT endpoint that updates only the fields the client sends.
- Add a DELETE endpoint that removes a book and returns 204.
- Test the full CRUD cycle end-to-end in Swagger UI.
Add the PUT update endpoint
When a client sends a PUT request, they might only want to change one field (like the price) without touching the rest. Your endpoint needs to apply only the fields that were actually included in the request body.
- In main.py, find the get_book function (the GET /books/{isbn} endpoint).
- Add the PUT endpoint below it by copying this code:
@app.put("/books/{isbn}", response_model=Book)
def update_book(isbn: str, book_update: BookUpdate):
# Return 404 if the book doesn't exist
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
existing_book = books_db[isbn]
# Get only the fields the client actually sent
update_data = book_update.model_dump(exclude_unset=True)
# Create a new book with those fields overridden
updated_book = existing_book.model_copy(update=update_data)
books_db[isbn] = updated_book
return updated_book
What does this code do?
This endpoint accepts a BookUpdate model where every field is optional. The magic happens in two Pydantic v2 methods:
- model_dump(exclude_unset=True) returns a dictionary containing only the fields the client actually included in their request. If they only sent a new price, only price appears in the dict.
- model_copy(update=update_data) creates a new model instance with those specific fields overridden, leaving everything else unchanged.
- Save main.py.
You won't test this endpoint alone yet. The next substep adds DELETE, and then you'll test the full cycle together.
Add the DELETE endpoint
The DELETE endpoint removes a book entirely and returns HTTP 204 (No Content). A 204 response means the server successfully processed the request but has no body to send back.
- In main.py, find the import line at the very top:
from fastapi import FastAPI, HTTPException
- Add Response to the import so the line looks like this:
from fastapi import FastAPI, HTTPException, Response
- Now scroll down to the update_book function you just added. Add the DELETE endpoint below it by copying this code:
@app.delete("/books/{isbn}", status_code=204)
def delete_book(isbn: str):
# Return 404 if the book doesn't exist
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
# Remove the book from the store
del books_db[isbn]
return Response(status_code=204)
Why return Response(status_code=204)?
FastAPI normally serializes your return value into a JSON response. For a 204 response, the HTTP spec says the body must be empty. Returning a Response object directly bypasses FastAPI's JSON serialization and sends a truly empty body.
- Save main.py.
- Stop the running server in Pydroid 3 (tap the stop button), then tap Run again to restart with your new endpoints.
Seeing an import error?
Make sure Response is on the same import line as FastAPI and HTTPException. All three come from the fastapi package.
If you see a syntax error, double-check that Response is separated by a comma in the import line. help me fix my FastAPI import error
Test the full CRUD cycle in Swagger UI
With all five endpoints in place, you can now walk through the complete lifecycle of a book resource. This mirrors exactly how a mobile app would interact with your API.
- Open your phone's browser and navigate to http://127.0.0.1:8000/docs.
- Expand the POST /books section and click Try it out.
- Replace the example JSON body with a new book:
{
"title": "Clean Code",
"author": "Robert C. Martin",
"price": 39.99,
"isbn": "978-0-13-235088-4",
"in_stock": true
}
- Click Execute. You should see a 201 response with your book returned.
- Expand GET /books/{isbn}, click Try it out, enter 978-0-13-235088-4 as the ISBN, and click Execute. You should see your book returned.
- Expand PUT /books/{isbn}, click Try it out, and enter the same ISBN.
- In the request body, send only a price update:
{
"price": 34.99
}
- Click Execute. The response should show the full book with only the price changed to 34.99. All other fields remain the same.
- Expand DELETE /books/{isbn}, click Try it out, enter 978-0-13-235088-4, and click Execute. You should see a 204 response with an empty body.
- Go back to GET /books/{isbn} and try fetching the same ISBN again. You should see a 404 response confirming the book is gone.
What just happened?
You walked through the complete lifecycle of a REST resource: created it (POST, 201), read it (GET, 200), modified it (PUT, 200), and removed it (DELETE, 204). Every real-world API your mobile apps consume follows this same pattern.
Notice how the PUT only changed the price because model_dump(exclude_unset=True) filtered out the fields you didn't send. This is how production APIs handle partial updates without overwriting data.
PUT returning the old values?
Make sure you're using model_dump(exclude_unset=True) and not model_dump(). Without exclude_unset=True, the dict includes all fields with their default None values, which would overwrite your existing data.
Also verify you restarted the server after saving your changes. help me debug my PUT endpoint
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and your server is running with all five endpoints visible 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, Response
from pydantic import BaseModel
app = FastAPI(title="Bookstore Inventory API", version="1.0.0")
# --- Data Models ---
class Book(BaseModel):
title: str
author: str
price: float
isbn: str
in_stock: bool = True
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
price: float | None = None
in_stock: bool | None = None
# --- In-Memory Data Store ---
books_db: dict[str, Book] = {
"978-0-13-468599-1": Book(
title="The Pragmatic Programmer",
author="David Thomas",
price=49.99,
isbn="978-0-13-468599-1",
),
"978-0-596-51774-8": Book(
title="JavaScript: The Good Parts",
author="Douglas Crockford",
price=29.99,
isbn="978-0-596-51774-8",
),
"978-1-49-195016-0": Book(
title="Fluent Python",
author="Luciano Ramalho",
price=59.99,
isbn="978-1-49-195016-0",
),
}
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Welcome to the Bookstore Inventory API"}
@app.get("/books", response_model=list[Book])
def get_all_books():
return list(books_db.values())
@app.post("/books", response_model=Book, status_code=201)
def create_book(book: Book):
if book.isbn in books_db:
raise HTTPException(status_code=409, detail="A book with this ISBN already exists")
books_db[book.isbn] = book
return book
@app.get("/books/{isbn}", response_model=Book)
def get_book(isbn: str):
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
return books_db[isbn]
@app.put("/books/{isbn}", response_model=Book)
def update_book(isbn: str, book_update: BookUpdate):
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
existing_book = books_db[isbn]
update_data = book_update.model_dump(exclude_unset=True)
updated_book = existing_book.model_copy(update=update_data)
books_db[isbn] = updated_book
return updated_book
@app.delete("/books/{isbn}", status_code=204)
def delete_book(isbn: str):
if isbn not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
del books_db[isbn]
return Response(status_code=204)
# --- Run Server ---
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Secret mission
Add Search and Filtering
Your API returns all books every time. Real-world mobile clients need to fetch only what they need. Can you add query parameters so consumers can search by author and filter by price range?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally on your Android device, so there are no ongoing costs. Decide whether to keep your files and packages installed, or delete them entirely.
Resources you used:
- The main.py file in Pydroid 3.
- The fastapi and uvicorn pip packages.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your API or want to reference the code later.
- Your main.py file stays in Pydroid 3, ready to run whenever you open the app.
- The fastapi and uvicorn packages remain installed for future projects.
Pause - I'll come back to this later
Stop the running server to free up memory. Your file and packages stay installed for next time.
- In Pydroid 3, tap the stop button in the terminal to kill the uvicorn server process.
- Close Pydroid 3 or swipe it away from your recent apps.
Delete - I don't want to use this again
Remove all project files and packages to start completely fresh.
- In Pydroid 3, open the file manager and delete main.py.
- Uninstall the pip packages by opening the Pydroid 3 terminal and running:
pip uninstall fastapi uvicorn -y
- Confirm the packages are removed by checking that no output appears when you run:
pip show fastapi uvicorn
Nice Work!
Nice Work!
You just built a complete REST API from scratch, running entirely on your Android device. You now understand both sides of the client-server relationship that powers every mobile app.
Here's what you accomplished:
- Built a FastAPI application with five endpoints covering all CRUD operations (Create, Read, Update, Delete) with proper HTTP status codes.
- Used Pydantic models for automatic request validation, seeing exactly how invalid data gets rejected with clear error messages.
- Explored the auto-generated Swagger UI documentation in your phone's browser, understanding the server-side perspective of API docs your mobile apps consume.
- Secret Mission: Added search and filtering with query parameters for case-insensitive author search and price range filtering.
Ready to quiz yourself?