Build a REST API with Python and Flask

Build a fully functional REST API for a book collection using Flask.

Introduction

30 Second Summary

Every time you open Instagram, search on Google, or stream a song on Spotify, your phone is talking to a server behind the scenes. That conversation happens through something called an API.

In this project, you will build a REST API from scratch using Python and Flask that manages a book collection with full Create, Read, Update, and Delete operations.

What You'll Build

A Book Collection REST API with five endpoints that handles CRUD operations, tested with Postman.

By the end of this project, you'll have:

  • A Flask application with five REST endpoints handling all CRUD operations.
  • A tested API with proper HTTP status codes and JSON responses.
  • A Postman collection with saved requests for each endpoint.
  • Secret Mission: Add a search endpoint that filters books by author or title using query parameters.

Are there any prerequisites?

You should be comfortable with basic Python concepts like variables, functions, dictionaries, and lists. No prior experience with web frameworks or APIs is needed.

Set Up Your Environment

Every REST API needs a server framework to handle incoming requests and route them to the right code.

In this step, you'll install Flask (a lightweight Python framework for building APIs), download Postman for testing your API, and create the project folder where your code will live.

In this step, get ready to:
  • Install Flask.
  • Download and install Postman.
  • Create your project folder and main file.
Install Flask using pip

Flask is the Python framework you'll use to define your API's routes and handle requests. You need to install it before writing any server code.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open a terminal inside VS Code by pressing Ctrl+` (macOS and Windows).
  • Install Flask by running this command:
pip install Flask==3.1.3

What does this command do?

`pip` is Python's package manager. It downloads and installs libraries from the internet.

`Flask==3.1.3` tells pip to install that exact version of Flask. Pinning the version ensures your project behaves consistently.

  • Verify Flask installed correctly by running:
python -c "import flask; print(flask.__version__)"

You should see 3.1.3 printed in your terminal.

Not seeing 3.1.3?

  • If you see a "command not found" error for pip, try using pip3 instead. Some systems use pip3 for Python 3.
  • If the verify command fails with "command not found" for python, try python3 instead.
  • If you see a permission error, try adding --user to the install command.

Help me troubleshoot my Flask installation.

Download and install Postman

Postman is a tool that lets you send requests to your API and inspect the responses. You'll use it throughout this project to test every endpoint you build.

macOS

  • Find the downloaded file in your Downloads folder and unzip it if your browser saved it as a zip file.
  • Open the Postman file to install it.
  • When prompted, move Postman to your Applications folder.

Windows

  • Run the downloaded .exe file.
  • The installer runs automatically. Wait for it to finish.
  • Launch Postman once the installation completes.
  • If prompted to create an account, select Skip and go to the app. You do not need an account for this project.

You should see the Postman workspace with an option to create new requests.

What is Postman?

Postman is a professional tool for testing APIs. Instead of writing code to send requests, you can use its visual interface to choose an HTTP method, enter a URL, and see the response instantly.

Think of it as a web browser designed specifically for APIs. A browser sends GET requests when you visit a URL. Postman lets you send any type of request (GET, POST, PUT, DELETE) with custom data.

Postman not launching?

  • On macOS, make sure you moved the app to your Applications folder before opening it.
  • On Windows, try right-clicking the installer and selecting Run as administrator.

Help me get Postman installed and running.

Create your project folder and main file

Now you need a dedicated folder for your API code. Keeping project files in their own folder prevents them from getting lost among other files.

  • Switch back to VS Code and click on your terminal at the bottom of the window.
  • 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
  • Create the main Python file for your API by running:
touch app.py
  • Confirm the file exists by running:
ls

You should see app.py in the output.

  • Now open the book-api folder in VS Code. Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the book-api folder, and click Open.

You should see app.py listed in the Explorer sidebar on the left side of VS Code.

Don't see app.py in the sidebar?

  • Make sure you opened the book-api folder itself, not your entire Desktop. The folder name should appear at the top of the Explorer sidebar.
  • If you accidentally opened the wrong folder, go to File then Open Folder again and select the correct one.

Help me find or open my book-api folder in VS Code.

Your environment is ready. Next up, you'll write your first Flask endpoint and see your API respond to requests in the browser.

Create Your First Endpoint

Your environment is ready. Flask is installed, Postman is waiting, and your project folder is open in VS Code.

Now it is time to make your computer respond to web requests. In this step, you will write a Flask server that stores book data and serves it as JSON to anyone who asks.

In this step, get ready to:
  • Write a Flask app that responds to browser requests.
  • Store sample book data in memory.
  • Create a GET endpoint and test it in Postman.
Write the basic Flask app structure

Every Flask app starts with two things: importing the framework and creating an app instance. The app instance is the object that handles incoming requests and sends back responses.

  • In VS Code, open app.py and paste the following code:
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route("/")
def home():
    return {"message": "Welcome to the Book Collection API"}

What does this code do?

  • from flask import Flask, request, jsonify imports the tools you need from the Flask library.
  • app = Flask(__name__) creates your app instance. Flask uses __name__ to know where to find your project files.
  • @app.route("/") tells Flask to run the home() function when someone visits the root URL. Returning a dictionary automatically converts it to a JSON response.
  • Save app.py.
  • Start the Flask development server by running this command in your VS Code terminal:
flask run --debug

What does this command do?

flask run starts a local development server. The --debug flag enables two features: the server automatically reloads when you change your code, and it shows helpful error messages in the browser if something breaks.

Because your file is named app.py, Flask automatically discovers it. No extra configuration needed.

You should see output like this in your terminal:

 * Serving Flask app 'app'
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
  • Open your browser and visit http://127.0.0.1:5000.

You should see a JSON response: {"message": "Welcome to the Book Collection API"}.

Don't see anything in the browser?

  • Make sure you saved app.py before running the command.
  • Check that your terminal is inside the book-api folder. Run cd book-api if needed.
  • On macOS, port 5000 may be used by AirPlay Receiver. Search for "AirPlay Receiver" in System Settings and toggle it off, then try again.

Help me debug my Flask server not starting.

Set up in-memory data storage

Your API needs data to serve. Instead of connecting to a database (which adds complexity), you will store books directly in a Python list. This keeps the focus on learning how REST endpoints work.

  • In app.py, find the line that says app = Flask(__name__). Add the following code below it (above the @app.route("/") line):
# In-memory book storage
books = [
    {"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937},
    {"id": 2, "title": "1984", "author": "George Orwell", "year": 1949},
    {"id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960}
]

next_id = 4

What does this code do?

books is a list of dictionaries. Each dictionary represents one book with four fields: id, title, author, and year.

next_id keeps track of what ID to assign when a new book is added later. Since you already have books with IDs 1, 2, and 3, the next one will be 4.

  • Save app.py.

You will not see a change in the browser yet. The next chunk uses this data to build a new endpoint.

Create a GET /books endpoint

Your home route proves the server works. Now you will create a proper API endpoint that returns all your book data as JSON.

  • In app.py, add the following code at the very bottom of the file (below the home() function):
@app.route("/books", methods=["GET"])
def get_books():
    return jsonify(books)

What does this code do?

@app.route("/books", methods=["GET"]) registers a new URL path at /books that only responds to GET requests. GET is the HTTP method browsers use when they want to read data.

jsonify(books) converts your Python list into a properly formatted JSON response with the correct headers. This is what makes your server speak the language that frontend apps and tools like Postman understand.

  • Save app.py. Your server restarts automatically thanks to debug mode.
  • Switch to Postman.
  • Click + to create a new request tab.
  • Make sure the method dropdown is set to GET.
  • Enter http://127.0.0.1:5000/books in the URL bar.
  • Click Send.

You should see a JSON array containing your three sample books in the response body at the bottom of the Postman window.

Seeing an empty response or error?

  • Make sure you saved app.py after adding the new route.
  • Check that your Flask server is still running in the terminal. If it stopped, run flask run --debug again.
  • Verify the URL is exactly http://127.0.0.1:5000/books with no trailing spaces or typos.

Help me troubleshoot my GET /books endpoint in Postman.

✔️ Awesome, I've got everything!

Great. Double check you have saved app.py and your server is still running.

ⓧ I'd like to double check the full code

Here is what your complete app.py should look like at this point:

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory book storage
books = [
    {"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937},
    {"id": 2, "title": "1984", "author": "George Orwell", "year": 1949},
    {"id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960}
]

next_id = 4


@app.route("/")
def home():
    return {"message": "Welcome to the Book Collection API"}


@app.route("/books", methods=["GET"])
def get_books():
    return jsonify(books)

Your API is alive and serving data. Next up, you will create an endpoint that fetches a single book by its ID.

Read a Single Book

Your /books endpoint returns the full collection every time. That works when a client wants to browse, but most of the time an app needs to fetch one specific resource.

In this step, you will create an endpoint that accepts a book ID in the URL and returns just that one book. You will also add proper error handling so the API responds clearly when someone requests a book that does not exist.

In this step, get ready to:
  • Create a GET endpoint that returns a single book by its ID.
  • Add a 404 error response for books that do not exist.
  • Test both valid and invalid requests in Postman.
Create the GET endpoint for a single book

Right now, a client can only get the entire list. You need a way to say "give me just the book with ID 2." Flask supports this through variable rules, where part of the URL becomes a parameter passed to your function.

  • In app.py, find the get_books() function at the bottom of the file.
  • Add the following new route below it:
@app.route("/books/<int:book_id>", methods=["GET"])
def get_book(book_id):
    # Search the books list for a matching ID
    book = next((b for b in books if b["id"] == book_id), None)
    return jsonify(book)

What does this code do?

The <int:book_id> part of the URL is a variable rule. Flask captures whatever integer appears in that position and passes it to your function as the book_id parameter.

The next() function searches through the books list using a generator expression. It returns the first book whose id matches book_id, or None if no match is found.

  • Save app.py. Your server auto-reloads in debug mode.
  • Switch to Postman and create a new GET request to http://127.0.0.1:5000/books/1.
  • Click Send. You should see just The Hobbit's data returned as JSON.

Seeing null in the response?

Make sure the URL ends with /books/1 (not /books/0). Your sample books start with ID 1, not 0.

If you see a connection error, check that your Flask server is still running in the terminal.

Help me figure out why my single book endpoint isn't returning data.

Add error handling for missing books

If someone requests a book ID that does not exist, the endpoint currently returns null with a 200 status. A well-designed API uses HTTP status codes to communicate what happened. A 404 status tells the client that the resource was not found.

  • In app.py, find this line inside your get_book function:
    return jsonify(book)
  • Replace that line with these three lines, so the function checks for None before returning:
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    return jsonify(book)

What does this code do?

The if book is None check catches the case where next() did not find a matching book. Instead of returning null, the API returns a clear error message.

The 404 at the end of the return tuple sets the HTTP status code. Flask lets you return a (response, status_code) tuple to override the default 200.

  • Save app.py.
  • In Postman, send a GET request to http://127.0.0.1:5000/books/1. You should see The Hobbit's data with a 200 status.
  • Now send a GET request to http://127.0.0.1:5000/books/99. You should see {"error": "Book not found"} with a 404 status in the response.

Still getting a 200 for an invalid ID?

Make sure you saved app.py after adding the if book is None check. Debug mode reloads automatically, but only after the file is saved.

Double-check that return jsonify({"error": "Book not found"}), 404 is indented inside the if block, and return jsonify(book) is at the same level as the if statement.

Help me debug my 404 error handling in Flask.

✔️ Awesome, I've got everything!

Great. Double check you have saved app.py before moving on.

ⓧ I'd like to double check the full code

Here is the full app.py as it should look at the end of this step:

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory book storage
books = [
    {"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937},
    {"id": 2, "title": "1984", "author": "George Orwell", "year": 1949},
    {"id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960}
]

next_id = 4


@app.route("/")
def home():
    return {"message": "Welcome to the Book Collection API"}


@app.route("/books", methods=["GET"])
def get_books():
    return jsonify(books)


@app.route("/books/<int:book_id>", methods=["GET"])
def get_book(book_id):
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    return jsonify(book)

Your API can now serve individual books and clearly communicates when something goes wrong. Next up, you will add the ability to create new books and delete existing ones.

Create and Delete Books

So far, your API can read books from the collection. Reading is only one part of the picture. A real API also needs to let clients add new books and remove existing ones.

In this step, you will build two new endpoints. A POST endpoint creates new books, and a DELETE endpoint removes them. Together with the GET endpoints from the previous step, this gives your API full CRUD capability (Create, Read, Update, Delete).

In this step, get ready to:
  • Create a POST endpoint that adds new books to the collection.
  • Create a DELETE endpoint that removes books by ID.
  • Test the full create-read-delete flow in Postman.
Create the POST endpoint for adding books

When a client wants to add a new book, it sends a POST request with the book's details in the request body as JSON. Your endpoint needs to read that data, assign a unique ID, and store the new book.

  • In app.py, scroll to the bottom of the file (below the get_book function) and add the POST endpoint by pasting this code:
@app.route("/books", methods=["POST"])
def create_book():
    global next_id
    # Read the JSON body from the client's request
    data = request.get_json()
    # Build a new book dict with an auto-assigned ID
    new_book = {
        "id": next_id,
        "title": data["title"],
        "author": data["author"],
        "year": data["year"]
    }
    # Add the new book to our list and increment the ID counter
    books.append(new_book)
    next_id += 1
    # Return the created book with a 201 Created status
    return jsonify(new_book), 201

What does this code do?

  • global next_id lets the function modify the module-level counter that tracks the next available ID.
  • request.get_json() reads the JSON body that the client sends with the POST request and converts it into a Python dictionary.
  • The new book dictionary pulls title, author, and year from the client's data, then pairs them with an auto-assigned id.
  • Returning with , 201 sets the HTTP status code to 201 Created, which tells the client the resource was successfully created.
  • Save app.py.
  • Check your terminal where Flask is running. You should see a "Restarting with stat" message, confirming that the debug server picked up your changes.

Seeing a syntax error in the terminal?

  • Make sure there are no extra indentation levels. The @app.route decorator should be at the top level (no indentation), and the function body should be indented one level.
  • Check that global next_id is the first line inside the function, before data = request.get_json().

Help me debug a syntax error in my Flask POST endpoint.

Create the DELETE endpoint and test in Postman

The DELETE endpoint removes a book from the collection by its ID. Like the GET single-book endpoint, it needs to handle the case where the requested book does not exist.

  • In app.py, add the DELETE endpoint below the create_book function you just wrote:
@app.route("/books/<int:book_id>", methods=["DELETE"])
def delete_book(book_id):
    global books
    # Find the book with the matching ID
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    # Rebuild the list without the deleted book
    books = [b for b in books if b["id"] != book_id]
    return jsonify({"message": f"Book '{book['title']}' deleted"})

What does this code do?

  • global books lets the function reassign the module-level books list (since we rebuild it without the deleted book).
  • The same next() pattern from get_book finds the book or returns None.
  • If the book is not found, the endpoint returns a 404 error.
  • A list comprehension rebuilds books excluding the deleted entry, effectively removing it.
  • The response includes the deleted book's title so the client gets a human-readable confirmation.
  • Save app.py. The server should reload again with no errors.

Now test both new endpoints in Postman.

  • Switch to Postman and open a new request tab.
  • Change the method dropdown from **GET** to **POST**.
  • Enter http://127.0.0.1:5000/books in the URL field.
  • Click the Body tab below the URL field.
  • Select raw, then choose JSON from the format dropdown to the right.
  • Paste this JSON into the body editor:
{
    "title": "Dune",
    "author": "Frank Herbert",
    "year": 1965
}
  • Click Send.

You should see a response with status 201 Created and the new book's data including "id": 4.

  • Now test the DELETE endpoint. Open a new request tab in Postman.
  • Change the method to **DELETE**.
  • Enter http://127.0.0.1:5000/books/4 in the URL field.
  • Click Send.

You should see a 200 response with the message "Book 'Dune' deleted".

  • Test the 404 case by sending a DELETE request to http://127.0.0.1:5000/books/99.

You should see a 404 response with "error": "Book not found".

POST returning a 405 Method Not Allowed?

  • Make sure you set the method dropdown in Postman to POST (not GET). Postman defaults to GET for new requests.
  • Check that your route decorator says methods=["POST"] (with square brackets and quotes around POST).
  • Confirm the server reloaded successfully after your last save. Look for a fresh "Running on" line in the terminal.

Help me debug why my POST request returns 405 in Flask.

Test the full CRUD flow in Postman

Now that both endpoints work individually, run through the full lifecycle to see how create and delete interact with the existing GET endpoints.

  • Send a GET request to http://127.0.0.1:5000/books. You should see the 3 original books.
  • Send a POST request to http://127.0.0.1:5000/books with this JSON body:
{
    "title": "The Great Gatsby",
    "author": "F. Scott Fitzgerald",
    "year": 1925
}
  • Click Send. You should receive the new book back with a 201 status and an assigned ID.
  • Send another GET request to http://127.0.0.1:5000/books. The list now includes 4 books with your new addition at the end.
  • Send a DELETE request to remove the book you just created. Use the ID from the POST response (for example, http://127.0.0.1:5000/books/5).
  • Send a final GET request to http://127.0.0.1:5000/books. The deleted book is gone and you are back to 3 books.

Why does the ID keep incrementing?

Even after deleting book 5, the next book you create will be book 6 (not 5). The next_id counter only goes up. This prevents ID reuse, which could confuse clients that cached the old book's data.

✔️ Awesome, I've got everything!

Great. Double check you have saved app.py and the server is still running.

ⓧ I'd like to double check the full code

Here is the full app.py up to this point:

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory book storage
books = [
    {"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937},
    {"id": 2, "title": "1984", "author": "George Orwell", "year": 1949},
    {"id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960}
]

next_id = 4


@app.route("/")
def home():
    return {"message": "Welcome to the Book Collection API"}


@app.route("/books", methods=["GET"])
def get_books():
    return jsonify(books)


@app.route("/books/<int:book_id>", methods=["GET"])
def get_book(book_id):
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    return jsonify(book)


@app.route("/books", methods=["POST"])
def create_book():
    global next_id
    data = request.get_json()
    new_book = {
        "id": next_id,
        "title": data["title"],
        "author": data["author"],
        "year": data["year"]
    }
    books.append(new_book)
    next_id += 1
    return jsonify(new_book), 201


@app.route("/books/<int:book_id>", methods=["DELETE"])
def delete_book(book_id):
    global books
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    books = [b for b in books if b["id"] != book_id]
    return jsonify({"message": f"Book '{book['title']}' deleted"})

Your API now supports creating and deleting books. Next up, you will add the final piece of CRUD: updating an existing book's data.

Update a Book

Your API can now create and delete books. There is one final CRUD operation missing: updating an existing book.

The PUT method lets a client send updated data for a resource that already exists. In this step, you will add the last endpoint to complete your API.

In this step, get ready to:
  • Create a PUT endpoint that updates a book's fields.
  • Test the update in Postman and verify the change persists.
Create the PUT endpoint

When a client sends a PUT request, it includes a JSON body with the fields it wants to change. Your endpoint needs to find the matching book, update only the fields the client sent, and return the updated book.

  • In app.py, add the following code below your delete_book function:
@app.route("/books/<int:book_id>", methods=["PUT"])
def update_book(book_id):
    # Find the book by ID
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    # Read the updated fields from the request body
    data = request.get_json()
    # Update only the fields the client sent, keep existing values as fallback
    book["title"] = data.get("title", book["title"])
    book["author"] = data.get("author", book["author"])
    book["year"] = data.get("year", book["year"])
    return jsonify(book)

What does this code do?

This endpoint handles three responsibilities in sequence:

  • It searches the books list for a book matching the ID from the URL. If no match is found, it returns a 404 error immediately.
  • data.get("title", book["title"]) is the key pattern here. It reads the title field from the client's JSON. If the client did not send that field, it keeps the book's existing value instead.
  • This means the client can update just one field (like the title) without having to resend every other field. This is a common REST convention for PUT endpoints.
  • Save app.py.
  • Your Flask server auto-reloads in debug mode. Check the terminal for a Restarting with stat message confirming the reload.

Seeing a syntax error in the terminal?

  • Make sure the @app.route decorator is on the line directly above def update_book(book_id): with no blank lines between them.
  • Check that the indentation inside the function uses consistent spaces (4 spaces per level).
  • Confirm the function is placed after the delete_book function and not accidentally nested inside it.

Why is my Flask server showing a syntax error?

Test the update in Postman

You will now send a PUT request to change a book's title, then verify the change stuck by fetching the book again.

  • Switch back to Postman.
  • Open a new request tab by clicking the + button.
  • Change the method dropdown from GET to PUT.
  • Enter http://127.0.0.1:5000/books/1 in the URL field.
  • Click the Body tab below the URL field.
  • Select raw, then change the dropdown on the right from Text to JSON.
  • Paste the following JSON into the body editor:
{
    "title": "The Lord of the Rings"
}
  • Click Send.

You should see a response with the updated book. The title changed to The Lord of the Rings, while the author and year stayed the same.

Why did only the title change?

Your data.get("title", book["title"]) pattern only overwrites a field if the client sends it. Since you only sent title in the JSON body, the author and year kept their original values.

  • Verify the change persists by sending a GET request to http://127.0.0.1:5000/books/1.
  • Confirm the response shows "title": "The Lord of the Rings" with the original author J.R.R. Tolkien.

Now test the error handling by sending a PUT request to a book that does not exist.

  • Change the URL to http://127.0.0.1:5000/books/999 and click Send.
  • You should see a 404 status code with the message {"error": "Book not found"}.

Getting a 405 Method Not Allowed?

  • Make sure the method dropdown in Postman says PUT, not POST or GET.
  • Check that your route decorator includes methods=["PUT"] in app.py.

Why am I getting a 405 Method Not Allowed from my Flask PUT endpoint?

✔️ Awesome, I've got everything!

Great. Double check you have saved app.py and that all five endpoints are working.

ⓧ I'd like to double check the full code

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory book storage
books = [
    {"id": 1, "title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937},
    {"id": 2, "title": "1984", "author": "George Orwell", "year": 1949},
    {"id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960}
]

next_id = 4


@app.route("/")
def home():
    return {"message": "Welcome to the Book Collection API"}


@app.route("/books", methods=["GET"])
def get_books():
    return jsonify(books)


@app.route("/books/<int:book_id>", methods=["GET"])
def get_book(book_id):
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    return jsonify(book)


@app.route("/books", methods=["POST"])
def create_book():
    global next_id
    data = request.get_json()
    new_book = {
        "id": next_id,
        "title": data["title"],
        "author": data["author"],
        "year": data["year"]
    }
    books.append(new_book)
    next_id += 1
    return jsonify(new_book), 201


@app.route("/books/<int:book_id>", methods=["DELETE"])
def delete_book(book_id):
    global books
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    books = [b for b in books if b["id"] != book_id]
    return jsonify({"message": f"Book '{book['title']}' deleted"})


@app.route("/books/<int:book_id>", methods=["PUT"])
def update_book(book_id):
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        return jsonify({"error": "Book not found"}), 404
    data = request.get_json()
    book["title"] = data.get("title", book["title"])
    book["author"] = data.get("author", book["author"])
    book["year"] = data.get("year", book["year"])
    return jsonify(book)

Secret mission

Add Search Functionality

Your API can list all books, but what if you have hundreds? Add a search endpoint that filters books by author or title using query parameters. This is how real APIs let clients find exactly what they need without downloading everything.

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:

  • Flask dev server (running in your terminal).
  • The book-api project folder on your Desktop.
  • Flask Python package (installed via pip).
  • Postman desktop application.

Keep everything running

No action needed. Choose this if you are still actively building or want to experiment with your API further.

  • Your Flask server keeps running in the terminal. You can send requests to it anytime.
  • Your book-api folder stays on your Desktop.
  • Flask and Postman remain installed and ready to use for future projects.

Pause - I'll come back to this later

Shut down the running server to free up your terminal. Your files and installed tools stay in place for when you return.

  • Switch to the terminal where your Flask server is running.
  • Press Ctrl+C to stop the server.
  • Your book-api folder, Flask package, and Postman all remain installed. When you come back, just run flask run --debug from inside your book-api folder to start the server again.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below to fully clean up.

  • Stop the Flask server by pressing Ctrl+C in the terminal where it is running.
  • Delete the project folder by running this command in your terminal:

macOS

rm -rf ~/Desktop/book-api

Windows

rmdir /s /q %USERPROFILE%\Desktop\book-api
  • Uninstall Flask by running:
pip uninstall Flask -y
  • Uninstall Postman:

macOS

  • Open Finder and go to Applications.
  • Find Postman and drag it to the Trash.
  • Empty the Trash to complete removal.

Windows

  • Open Settings.
  • Select Apps, then Apps & features.
  • Find Postman in the list, click it, and select Uninstall.

Nice Work!

Nice Work!

You just built a real REST API from scratch. You now have a fully functional Book Collection API running locally that handles every CRUD operation, tested and verified with a professional API client.

You've learned how to:

  • Built a complete REST API with 5 endpoints handling Create, Read, Update, and Delete operations for a book collection.
  • Tested every endpoint using Postman, working with JSON request and response bodies and HTTP status codes like 200, 201, and 404.
  • Used Flask routing, variable rules, and request handling to process GET, POST, PUT, and DELETE requests.
  • Secret Mission: Added query parameter-based search filtering to your API using request.args and case-insensitive partial matching.

Ready to quiz yourself?