Build a REST API with Python and FastAPI

Build a bookmarks REST API with full CRUD operations using FastAPI.

Introduction

30 Second Summary

Every time you open a mobile app, check the weather, or scroll social media, your device is sending requests to a server and getting structured data back. The technology behind this is called a REST API.

In this project, you will build a REST API from scratch using Python and FastAPI. Your API will handle saving, retrieving, updating, and deleting web bookmarks, complete with automatic data validation and interactive documentation you can test right in your browser.

What You'll Build

A Bookmarks API with five endpoints that responds to HTTP requests with JSON data, validates incoming requests automatically, and serves its own interactive documentation.

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

  • A FastAPI application with five REST endpoints covering full CRUD operations (Create, Read, Update, Delete).
  • Automatic data validation powered by Pydantic models that reject malformed requests with clear error messages.
  • Auto-generated Swagger UI documentation at /docs that lets you test every endpoint directly in your browser.
  • Secret Mission: Add search and filtering to find bookmarks by keyword using query parameters.

Are there any prerequisites?

None. This project includes full setup instructions for Python, VS Code, and all required packages. You do not need any prior programming experience with APIs.

Set Up Python and Your Project

Before you can build your API, you need the right tools on your machine. Python is the programming language your API will be written in. FastAPI is the framework that makes building APIs fast and straightforward.

In this step, you will install Python, set up VS Code as your code editor, and create an isolated project folder with everything FastAPI needs to run.

In this step, get ready to:
  • Install Python 3.10+ and verify it works in your terminal.
  • Install VS Code with the Python extension.
  • Create your project folder, virtual environment, and install FastAPI.
Install Python

Python is the language your entire API runs on. You need version 3.10 or higher for FastAPI to work correctly.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Windows Terminal (Windows) and press Enter to open it.
  • Check if Python is installed by running the version check command for your operating system:

macOS

python3 --version

Windows

python --version

✔️ I see version 3.10 or higher

You should see output like Python 3.10.x or Python 3.12.x (any number at or above 3.10 works). Python is ready to go.

ⓧ I see an older version

If you see a version number below 3.10 (for example Python 3.8.x), you need to install a newer version.

  • Go to https://www.python.org/downloads in your browser.
  • Download the latest Python installer for your operating system.
  • Run the installer. On Windows, check the box that says Add Python to PATH before clicking Install Now.
  • Close and reopen your terminal, then run the version check again to confirm it now shows 3.10 or higher.

Why close and reopen the terminal?

Your terminal loads the PATH (where it looks for programs) when it first opens. After installing Python, the terminal won't know about it until you restart it.

ⓧ Command not found

Python is not installed on your machine yet.

  • Go to https://www.python.org/downloads in your browser.
  • Download the latest Python installer for your operating system.
  • Run the installer. On Windows, check the box that says Add Python to PATH before clicking Install Now.
  • Close and reopen your terminal, then run the version check command again.
  • You should now see a version number of 3.10 or higher.
Install VS Code

VS Code is a free code editor that makes writing Python easy with features like syntax highlighting and error detection.

  • Go to https://code.visualstudio.com in your browser.
  • Download and install VS Code for your operating system.
  • Once installed, open VS Code. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
  • Type Python in the search bar at the top of the panel.
  • Find the extension named Python published by Microsoft and click Install.

Why the Python extension?

The Python extension gives VS Code the ability to understand Python code. It provides autocomplete suggestions, highlights errors as you type, and helps you run Python files directly from the editor.

Create your project folder and install FastAPI

Every Python project should live in its own folder with its own virtual environment. A virtual environment is an isolated space where you install packages without affecting the rest of your system.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir bookmarks-api
cd bookmarks-api
  • Confirm you are inside the bookmarks-api folder by running:
ls

The output should be empty since the folder is brand new.

  • Create a virtual environment and activate it by running the commands for your operating system:

macOS

python3 -m venv .venv
source .venv/bin/activate

Windows

python -m venv .venv
.venv\Scripts\activate

What does this do?

The first command creates a .venv folder inside your project. This folder holds a private copy of Python and any packages you install.

The second command activates the environment. You should see (.venv) appear at the beginning of your terminal prompt. This confirms that any packages you install will go into this project only.

Don't see (.venv) in your prompt?

On Windows, if you see an error about execution policies, run this command first and then try the activate command again:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

On macOS, make sure you typed source before the path. Without it, the activation will not work. ask me for help activating my virtual environment

  • Install FastAPI and its standard dependencies by running:
pip install "fastapi[standard]"

You will see a series of download and install messages scroll by. The install is complete when you see your terminal prompt return.

What is fastapi[standard]?

The [standard] part tells pip to install FastAPI along with extra packages it needs to run a development server, handle requests, and serve documentation. This saves you from installing each dependency separately.

Seeing install errors?

Make sure you see (.venv) in your terminal prompt before running the install. If it is missing, your virtual environment is not active. Go back to the activation step above.

If you see a network error, check your internet connection and try the command again. ask me for help with FastAPI installation errors

  • Confirm FastAPI installed successfully by running:
pip show fastapi

You should see output showing the package name, version, and other details. The version should be 0.136.1 or higher.

Your development environment is ready. Next up, you will create your first API endpoint and see it respond to requests in your browser.

Create Your First Endpoint

Your Python environment is set up and FastAPI is installed. Now it's time to write your first line of API code.

An endpoint is a specific URL that your API responds to. You'll create one endpoint, run the server, and see JSON data appear in your browser.

In this step, get ready to:
  • Create main.py with a FastAPI application and a root endpoint.
  • Run the development server and see a JSON response in your browser.
  • Explore the auto-generated API documentation.
Create the FastAPI application file

Every FastAPI project starts with an application instance and at least one endpoint. Your first file will define both.

  • In VS Code, click File in the top menu bar.
  • Click New File.
  • Name the file main.py and save it inside your bookmarks-api folder (the same folder that contains .venv).
  • Paste the following code into main.py:
from fastapi import FastAPI

# Create the app instance with a title and version
app = FastAPI(title="Bookmarks API", version="1.0.0")


# Define a GET endpoint at the root URL
@app.get("/")
def root():
    return {"message": "Welcome to the Bookmarks API"}

What does this code do?

FastAPI() creates your application instance. The title and version parameters label your API in the auto-generated documentation.

@app.get("/") is a decorator that tells FastAPI to call the root() function whenever someone sends a GET request to /. The function returns a Python dictionary, which FastAPI automatically converts to JSON.

  • Save main.py.

You won't see a change yet. The next substep runs this file as a server.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

from fastapi import FastAPI

app = FastAPI(title="Bookmarks API", version="1.0.0")


@app.get("/")
def root():
    return {"message": "Welcome to the Bookmarks API"}
Run the development server

FastAPI includes a built-in development server that auto-reloads whenever you change your code. One command starts everything.

  • In your terminal (with the .venv virtual environment still active), start the server by running:
fastapi dev main.py

What does this command do?

fastapi dev launches a development server powered by Uvicorn. It automatically detects your FastAPI app in main.py and serves it at http://127.0.0.1:8000.

The dev mode enables auto-reload. Every time you save a change to your code, the server restarts automatically.

  • Open your browser and visit http://127.0.0.1:8000.

You should see a JSON response in your browser:

{"message":"Welcome to the Bookmarks API"}

Don't see the JSON response?

  • Make sure your terminal still shows the server running. If you see an error, check that your virtual environment is active (you should see (.venv) at the start of your terminal prompt).
  • Confirm main.py is saved in the bookmarks-api folder, not inside .venv.
  • Check for typos in the URL. It must be http://127.0.0.1:8000 (not https).

help me debug why my FastAPI server isn't responding at localhost:8000

Explore the auto-generated documentation

FastAPI automatically creates interactive API documentation from your code. You didn't write any extra configuration for this.

  • In your browser, visit http://127.0.0.1:8000/docs.

You should see Swagger UI loaded in your browser. It shows your API title ("Bookmarks API"), version ("1.0.0"), and the GET / endpoint you just created.

What is Swagger UI?

Swagger UI is an interactive documentation page where you can see all your endpoints and test them directly in the browser. You'll use this throughout the project to send requests without needing any extra tools.

FastAPI generates this from your Python code automatically. Every endpoint you add will appear here with its parameters, request body, and response schema.

  • Click the GET / section to expand it.
  • Click Try it out, then click Execute.

You should see the same JSON response appear in the Response body section below.

Your first endpoint is live and documented. Next up, you'll define a data model so your API knows exactly what a bookmark looks like.

Define Your Data Model

Your API has a root endpoint, but it doesn't know what a bookmark looks like yet. Before you can store or retrieve bookmarks, you need to define the shape of your data in code.

FastAPI uses Pydantic models to define data shapes. You write a Python class describing the fields you expect, and FastAPI automatically validates every incoming request against that shape.

In this step, get ready to:
  • Define Pydantic models for bookmark data.
  • Set up in-memory storage and a GET endpoint.
  • Test the new endpoint in Swagger UI.
Create your Pydantic data models

A Pydantic model is a Python class that declares what fields a piece of data must have and what types those fields are. You need two models: one that describes what a user sends when creating a bookmark, and one that describes what the API sends back.

  • In main.py, find the first line (from fastapi import FastAPI) and add this import directly below it:
from pydantic import BaseModel
  • Below the app = FastAPI(title="Bookmarks API", version="1.0.0") line, add the BookmarkCreate model by pasting this code:
class BookmarkCreate(BaseModel):
    # The fields a user must send when creating a bookmark
    title: str
    url: str
    description: str | None = None

What does this code do?

BookmarkCreate inherits from BaseModel, which makes it a Pydantic model. Each field declares a type that FastAPI will enforce automatically.

  • title: str means the user must send a string for the title.
  • url: str means the user must send a string for the URL.
  • description: str | None = None means the description is optional. If the user doesn't send one, it defaults to None.
  • Directly below BookmarkCreate, add the BookmarkResponse model by pasting this code:
class BookmarkResponse(BaseModel):
    # The fields the API sends back (includes the generated id)
    id: int
    title: str
    url: str
    description: str | None = None

Why two separate models?

BookmarkCreate is the shape of data coming IN from the user. BookmarkResponse is the shape of data going OUT from the API.

The response includes an id field that the server generates. The user never sends an ID when creating a bookmark. Separating input from output is a common pattern in real APIs.

  • Save main.py.

Seeing a red underline on str | None?

The str | None syntax requires Python 3.10 or higher. Check your Python version by running python --version in your terminal. If you see a version lower than 3.10, you need to install a newer version of Python.

help me fix a syntax error with str | None in my Pydantic model

Add storage and a GET endpoint

Your models define the shape of bookmark data, but you need somewhere to store the actual bookmarks. For this project, you will use a simple Python list that lives in memory.

  • In main.py, find the BookmarkResponse class you just added and paste this code below it (leave a blank line after the class):
# In-memory storage for bookmarks
bookmarks: list[dict] = []
next_id: int = 1
  • Scroll to the bottom of main.py (below the existing root() function) and add this new endpoint:
@app.get("/bookmarks", response_model=list[BookmarkResponse])
def get_bookmarks():
    # Return all stored bookmarks
    return bookmarks

What does this code do?

bookmarks: list[dict] = [] creates an empty list to hold your bookmarks. next_id: int = 1 is a counter that assigns each new bookmark a unique ID.

The response_model=list[BookmarkResponse] parameter tells FastAPI to format the response as a list of BookmarkResponse objects. This also adds the response schema to your auto-generated documentation.

  • Save main.py.

Seeing an import error or NameError?

Make sure your from pydantic import BaseModel line is at the top of the file, directly below from fastapi import FastAPI. Also confirm that the BookmarkResponse class is defined above the get_bookmarks() function, since Python reads files top to bottom.

help me fix an import or name error in my FastAPI bookmark models

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py before moving on to testing.

ⓧ I'd like to double check the full code

Here is the full main.py file at this point. Compare it with yours:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title="Bookmarks API", version="1.0.0")


class BookmarkCreate(BaseModel):
    title: str
    url: str
    description: str | None = None


class BookmarkResponse(BaseModel):
    id: int
    title: str
    url: str
    description: str | None = None


bookmarks: list[dict] = []
next_id: int = 1


@app.get("/")
def root():
    return {"message": "Welcome to the Bookmarks API"}


@app.get("/bookmarks", response_model=list[BookmarkResponse])
def get_bookmarks():
    return bookmarks
Test the endpoint in Swagger UI

Your development server auto-reloads whenever you save a file. If you stopped the server earlier, restart it now.

  • If your development server is not running, start it by running this command in your terminal:
fastapi dev main.py
  • Open your browser and go to http://127.0.0.1:8000/docs.
  • Find the GET /bookmarks endpoint in the list.
  • Click the endpoint row to expand it.
  • Click Try it out, then click Execute.

You should see a response body of [] (an empty list) with a 200 status code. That is correct. You have not created any bookmarks yet, so the list is empty.

What is Swagger UI?

Swagger UI is an interactive documentation page that FastAPI generates automatically from your code. Every endpoint, model, and parameter you define appears here.

You can test your API directly in the browser without installing any extra tools. The Try it out button sends a real request to your running server and shows you the response.

Don't see the GET /bookmarks endpoint?

Make sure you saved main.py after adding the endpoint. The dev server auto-reloads on save, so a hard refresh of the /docs page should show the new endpoint.

If the server crashed, check your terminal for error messages. A common issue is a missing blank line between the class definitions and the function.

help me fix my FastAPI server so the GET /bookmarks endpoint shows up in Swagger UI

Your data models and first real endpoint are in place. Next up, you will add endpoints to create and read individual bookmarks.

Add Create and Read Endpoints

Your API can list all bookmarks, but right now there's no way to actually create one or look up a specific bookmark.

In this step, you'll add a POST endpoint that accepts new bookmark data and a GET endpoint that retrieves a single bookmark by its ID. You'll also see how FastAPI automatically rejects invalid data with clear error messages.

In this step, get ready to:
  • Add a POST endpoint that creates new bookmarks.
  • Add a GET endpoint that retrieves a single bookmark by ID.
  • Test automatic validation by sending invalid data.
Add a POST endpoint for creating bookmarks

A GET endpoint retrieves data, but a POST endpoint accepts new data from the client. When someone sends a POST request to your API, they include a JSON body containing the bookmark details.

  • In main.py, scroll to the bottom of the file (below the get_bookmarks function).
  • Add the POST endpoint by copying this code at the end of the file:
@app.post("/bookmarks", response_model=BookmarkResponse, status_code=201)
def create_bookmark(bookmark: BookmarkCreate):
    global next_id
    # Build a new bookmark dict with an auto-assigned ID
    new_bookmark = {
        "id": next_id,
        "title": bookmark.title,
        "url": bookmark.url,
        "description": bookmark.description,
    }
    # Store it and increment the ID counter
    bookmarks.append(new_bookmark)
    next_id += 1
    return new_bookmark

What does this code do?

This endpoint does several things at once:

  • @app.post("/bookmarks") tells FastAPI this function handles POST requests to /bookmarks.
  • status_code=201 returns HTTP 201 (Created) instead of the default 200, which is the standard response when a new resource is successfully created.
  • bookmark: BookmarkCreate tells FastAPI to parse the request body as a BookmarkCreate model. If the data doesn't match, FastAPI rejects it automatically.
  • global next_id lets the function modify the module-level next_id counter so each bookmark gets a unique ID.
  • Save main.py.
  • Check your terminal. The dev server should automatically reload (you'll see a line mentioning Reloading or Started reloader).
  • Refresh http://127.0.0.1:8000/docs in your browser. You should see a new green POST /bookmarks section in the Swagger UI.

Don't see the new endpoint?

  • Make sure you saved main.py. Unsaved changes won't trigger a server reload.
  • Check your terminal for a syntax error. A missing colon or mismatched indentation will prevent the reload.
  • Make sure the new code is outside (below) the get_bookmarks function, not indented inside it.

Why isn't my POST endpoint showing up in Swagger UI?

Add a GET endpoint for a single bookmark

Listing all bookmarks is useful, but you also need a way to retrieve one specific bookmark by its ID. This is called a path parameter. If a bookmark doesn't exist, your API should return a 404 error instead of crashing.

FastAPI provides HTTPException for returning error responses. You need to add it to your import line first.

  • In main.py, find the first line at the top of your file:
from fastapi import FastAPI
  • Update it to also import HTTPException, so the line now looks like this:
from fastapi import FastAPI, HTTPException
  • Now scroll to the bottom of main.py (below the create_bookmark function you just added).
  • Add the GET by ID endpoint by copying this code at the end of the file:
@app.get("/bookmarks/{bookmark_id}", response_model=BookmarkResponse)
def get_bookmark(bookmark_id: int):
    # Search for a bookmark with the matching ID
    for bookmark in bookmarks:
        if bookmark["id"] == bookmark_id:
            return bookmark
    # If no match found, return a 404 error
    raise HTTPException(status_code=404, detail="Bookmark not found")

What does this code do?

  • {bookmark_id} in the URL path tells FastAPI to capture that part of the URL as a variable. A request to /bookmarks/3 passes 3 as bookmark_id.
  • bookmark_id: int tells FastAPI to convert the URL segment to an integer automatically.
  • raise HTTPException(status_code=404) sends back a proper 404 Not Found response when the bookmark doesn't exist.
  • Save main.py.
  • Refresh http://127.0.0.1:8000/docs in your browser. You should now see a blue GET /bookmarks/{bookmark_id} section in the Swagger UI.

Seeing an error in the terminal?

  • If you see NameError: name 'HTTPException' is not defined, go back to line 1 and make sure you updated the import to from fastapi import FastAPI, HTTPException.
  • Check that the new function is not indented inside create_bookmark. It should start at the same indentation level (no leading spaces before the @ symbol).

Help me fix an error with my GET /bookmarks/{bookmark_id} endpoint.

Test your endpoints in Swagger UI

Swagger UI lets you test your API directly in the browser without installing any extra tools. You'll create a bookmark, retrieve it by ID, and then see what happens when you send invalid data.

  • In your browser at http://127.0.0.1:8000/docs, click on the green POST /bookmarks section to expand it.
  • Click Try it out.
  • Replace the example JSON in the request body with this:
{
  "title": "FastAPI Docs",
  "url": "https://fastapi.tiangolo.com",
  "description": "Official FastAPI documentation"
}
  • Click Execute.

You should see a response with status code 201 and a JSON body that includes your bookmark data plus an id field set to 1.

  • Now click on the blue GET /bookmarks/{bookmark_id} section to expand it.
  • Click Try it out.
  • Enter 1 in the bookmark_id field.
  • Click Execute.

You should see the same bookmark returned with status code 200.

  • Try entering 999 in the bookmark_id field and click Execute again.

You should see a 404 response with the detail Bookmark not found. Your error handling is working.

  • Finally, go back to the POST /bookmarks section.
  • Click Try it out and replace the request body with this invalid JSON (missing the required url field):
{
  "title": "Missing URL bookmark"
}
  • Click Execute.

You should see a 422 validation error response. FastAPI automatically checked the data against your Pydantic model and rejected it because url is required.

Why is automatic validation useful?

You didn't write a single line of validation code. FastAPI and Pydantic handled it for you based on the type annotations in your BookmarkCreate model.

The 422 response even tells the client exactly which field is missing and why. In production APIs, this saves hours of writing manual validation logic.

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py and your server is still running.

ⓧ I'd like to double check the full code

Here's what your complete main.py should look like at this point:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Bookmarks API", version="1.0.0")


class BookmarkCreate(BaseModel):
    title: str
    url: str
    description: str | None = None


class BookmarkResponse(BaseModel):
    id: int
    title: str
    url: str
    description: str | None = None


bookmarks: list[dict] = []
next_id: int = 1


@app.get("/")
def root():
    return {"message": "Welcome to the Bookmarks API"}


@app.get("/bookmarks", response_model=list[BookmarkResponse])
def get_bookmarks():
    return bookmarks


@app.post("/bookmarks", response_model=BookmarkResponse, status_code=201)
def create_bookmark(bookmark: BookmarkCreate):
    global next_id
    new_bookmark = {
        "id": next_id,
        "title": bookmark.title,
        "url": bookmark.url,
        "description": bookmark.description,
    }
    bookmarks.append(new_bookmark)
    next_id += 1
    return new_bookmark


@app.get("/bookmarks/{bookmark_id}", response_model=BookmarkResponse)
def get_bookmark(bookmark_id: int):
    for bookmark in bookmarks:
        if bookmark["id"] == bookmark_id:
            return bookmark
    raise HTTPException(status_code=404, detail="Bookmark not found")

Your API can now create bookmarks and retrieve them individually. Next up, you'll complete the full CRUD cycle by adding update and delete endpoints.

Add Update and Delete Endpoints

Your API can create and retrieve bookmarks. That covers the C and R in CRUD, but real-world APIs also need to modify and remove data.

In this step, you will add a PUT endpoint (to update an existing bookmark) and a DELETE endpoint (to remove one). Once these are in place, your API covers the full CRUD lifecycle.

In this step, get ready to:
  • Add a PUT endpoint to update bookmarks.
  • Add a DELETE endpoint to remove bookmarks.
  • Test the full CRUD cycle in Swagger UI.
Add the PUT endpoint for updating bookmarks

A PUT request replaces an existing resource with new data. If the bookmark does not exist, the API returns a 404 error.

  • In main.py, scroll to the bottom of the file (below the get_bookmark function) and add this code:
# Update an existing bookmark by ID
@app.put("/bookmarks/{bookmark_id}", response_model=BookmarkResponse)
def update_bookmark(bookmark_id: int, updated: BookmarkCreate):
    for bookmark in bookmarks:
        if bookmark["id"] == bookmark_id:
            # Overwrite each field with the incoming data
            bookmark["title"] = updated.title
            bookmark["url"] = updated.url
            bookmark["description"] = updated.description
            return bookmark
    raise HTTPException(status_code=404, detail="Bookmark not found")

What does this code do?

This endpoint accepts two inputs: the bookmark's ID from the URL path, and a full BookmarkCreate body with the new data.

  • It loops through the bookmarks list looking for a matching ID.
  • When found, it overwrites the title, URL, and description fields, then returns the updated bookmark.
  • If no match is found, it raises a 404 error. The same pattern you used in get_bookmark.
  • Save main.py.
  • Your dev server auto-reloads. Visit http://127.0.0.1:8000/docs and confirm you see a new PUT /bookmarks/{bookmark_id} row in the endpoint list.

Don't see the PUT endpoint?

  • Check your terminal for error messages. A syntax error will prevent the server from reloading.
  • Make sure the new function is at the bottom of the file, outside any other function body.
  • Confirm you saved the file. Unsaved changes will not trigger a reload.

Help me debug why my PUT endpoint is not showing up in Swagger UI.

Add the DELETE endpoint for removing bookmarks

A DELETE request removes a resource entirely. The standard response for a successful delete is HTTP 204 (No Content), meaning the server processed the request but has nothing to send back.

  • Below the update_bookmark function you just added, paste this code:
# Delete a bookmark by ID
@app.delete("/bookmarks/{bookmark_id}", status_code=204)
def delete_bookmark(bookmark_id: int):
    # enumerate gives us both the index and the bookmark dict
    for i, bookmark in enumerate(bookmarks):
        if bookmark["id"] == bookmark_id:
            # Remove the bookmark from the list by index
            bookmarks.pop(i)
            return
    raise HTTPException(status_code=404, detail="Bookmark not found")

What does this code do?

The status_code=204 in the decorator tells FastAPI that a successful delete returns no body. The function uses return with no value to match this.

  • enumerate gives you both the index (i) and the item. You need the index to call bookmarks.pop(i), which removes the item from the list.
  • If no bookmark matches the ID, a 404 error is raised just like in the other endpoints.
  • Save main.py.
  • Refresh http://127.0.0.1:8000/docs and confirm you see both PUT /bookmarks/{bookmark_id} and DELETE /bookmarks/{bookmark_id} in the list.

Seeing an error in your terminal?

  • A common cause is a missing colon at the end of the def line or the if line.
  • Make sure the indentation inside the for loop uses 4 spaces consistently. Mixing tabs and spaces causes errors.

Help me fix a syntax error in my DELETE endpoint.

Test the full CRUD cycle in Swagger UI

Your API now supports all four CRUD operations. Time to test them as a complete workflow.

  • In Swagger UI at http://127.0.0.1:8000/docs, expand the POST /bookmarks endpoint.
  • Click Try it out, then paste this JSON into the request body:
{
  "title": "FastAPI Docs",
  "url": "https://fastapi.tiangolo.com",
  "description": "Official FastAPI documentation"
}
  • Click Execute. You should see a 201 response with the bookmark and an id of 1.
  • Expand GET /bookmarks/{bookmark_id}, click Try it out, enter 1 for the ID, and click Execute. You should see the bookmark you just created.
  • Expand PUT /bookmarks/{bookmark_id}, click Try it out, enter 1 for the ID, and paste this updated JSON:
{
  "title": "FastAPI Docs - Updated",
  "url": "https://fastapi.tiangolo.com",
  "description": "The official docs for building APIs with FastAPI"
}
  • Click Execute. You should see a 200 response with the updated title and description.
  • Expand DELETE /bookmarks/{bookmark_id}, click Try it out, enter 1 for the ID, and click Execute. You should see a 204 response with no body.
  • Confirm the delete worked by expanding GET /bookmarks, clicking Try it out, and clicking Execute. You should see an empty list [].

Why test as a cycle?

Testing Create, Read, Update, Delete in sequence proves your endpoints work together. It catches bugs where one endpoint accidentally interferes with another's data.

Getting a 404 when you update or delete?

  • Remember that the in-memory list resets every time the server reloads. If you saved a file between creating and updating, create a fresh bookmark first.
  • Double-check the ID number. It must match the id field returned when you created the bookmark.

Help me understand why I'm getting 404 errors when testing my CRUD endpoints.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and your dev server is running without errors.

ⓧ I'd like to double check the full code

Here is the full main.py at this point:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Bookmarks API", version="1.0.0")


class BookmarkCreate(BaseModel):
    title: str
    url: str
    description: str | None = None


class BookmarkResponse(BaseModel):
    id: int
    title: str
    url: str
    description: str | None = None


bookmarks: list[dict] = []
next_id: int = 1


@app.get("/")
def root():
    return {"message": "Welcome to the Bookmarks API"}


@app.get("/bookmarks", response_model=list[BookmarkResponse])
def get_bookmarks():
    return bookmarks


@app.post("/bookmarks", response_model=BookmarkResponse, status_code=201)
def create_bookmark(bookmark: BookmarkCreate):
    global next_id
    new_bookmark = {
        "id": next_id,
        "title": bookmark.title,
        "url": bookmark.url,
        "description": bookmark.description,
    }
    bookmarks.append(new_bookmark)
    next_id += 1
    return new_bookmark


@app.get("/bookmarks/{bookmark_id}", response_model=BookmarkResponse)
def get_bookmark(bookmark_id: int):
    for bookmark in bookmarks:
        if bookmark["id"] == bookmark_id:
            return bookmark
    raise HTTPException(status_code=404, detail="Bookmark not found")


@app.put("/bookmarks/{bookmark_id}", response_model=BookmarkResponse)
def update_bookmark(bookmark_id: int, updated: BookmarkCreate):
    for bookmark in bookmarks:
        if bookmark["id"] == bookmark_id:
            bookmark["title"] = updated.title
            bookmark["url"] = updated.url
            bookmark["description"] = updated.description
            return bookmark
    raise HTTPException(status_code=404, detail="Bookmark not found")


@app.delete("/bookmarks/{bookmark_id}", status_code=204)
def delete_bookmark(bookmark_id: int):
    for i, bookmark in enumerate(bookmarks):
        if bookmark["id"] == bookmark_id:
            bookmarks.pop(i)
            return
    raise HTTPException(status_code=404, detail="Bookmark not found")

Secret mission

Add Search and Filtering

Your API can store bookmarks, but what happens when you have hundreds? Right now, the only way to find a specific bookmark is to scroll through all of them. Add a search feature that lets users filter bookmarks by keyword, using query parameters.

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 bookmarks-api project folder (contains your code, virtual environment, and installed packages).
  • The FastAPI development server (running process in your terminal).

Keep everything running

No action needed. Choose this if you're still actively building or want to experiment with the API further.

  • Your project folder stays on your Desktop.
  • If the dev server is still running, you can keep using it at http://127.0.0.1:8000/docs.
  • To restart the server later, open a terminal, navigate to bookmarks-api, activate the virtual environment, and run fastapi dev main.py.

Pause - I'll come back to this later

Stop the running server to free up your terminal. Your files stay intact for next time.

  • In the terminal where the server is running, press Ctrl+C to stop the FastAPI development server.
  • Close the terminal window.
  • Your bookmarks-api folder and virtual environment remain on your Desktop, ready for next time.

Delete - I don't want to use this again

Remove all project files from your machine. This cannot be undone.

  • In the terminal where the server is running, press Ctrl+C to stop the FastAPI development server.
  • If your virtual environment is still activated, deactivate it by running:
deactivate
  • Delete the project folder. On macOS or Linux, run:
cd ~/Desktop
rm -rf bookmarks-api
  • On Windows, run:
cd ~\Desktop
rmdir /s /q bookmarks-api
  • This removes all your code, the virtual environment, and the installed packages in one step.

Nice Work!

Nice Work!

You just built a real REST API from scratch. You now have a fully functional FastAPI application running locally that handles Create, Read, Update, and Delete operations with automatic data validation and interactive documentation.

You learned how to:

  • Built a FastAPI application with 5 REST endpoints covering full CRUD operations, using proper HTTP status codes (200, 201, 204, 404, 422) for each operation.
  • Used Pydantic models to validate incoming request data automatically, getting clear 422 error responses for malformed input with zero extra code.
  • Generated interactive Swagger UI documentation at /docs and tested every endpoint directly in your browser.
  • Secret Mission: Added search and filtering functionality using query parameters to find bookmarks by keyword.

Ready to quiz yourself?