Build a REST API with Python & FastAPI

Build a task manager REST API with full CRUD operations and Swagger docs.

Introduction

30 Second Summary

Every app you use daily (to-do lists, social media feeds, online shopping carts) relies on APIs behind the scenes to create, read, update, and delete data. Understanding how these APIs work is one of the most in-demand skills in software development.

In this project, you will build a Task Manager REST API from scratch using Python and FastAPI. You'll create endpoints that handle creating, reading, updating, and deleting tasks, then interact with auto-generated documentation in your browser.

What You'll Build

A fully functional REST API that manages tasks through HTTP requests, complete with data validation and auto-generated interactive documentation.

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

  • A Python file (main.py) containing your complete API with 4 endpoints covering all CRUD operations (Create, Read, Update, Delete).
  • Auto-generated Swagger UI documentation you can interact with at http://127.0.0.1:8000/docs.
  • A solid understanding of variables, functions, decorators, and HTTP methods and how they work together to form an API.
  • Secret Mission: Add a filter endpoint that lets you search tasks by status.

Are there any prerequisites?

None. This project starts from zero and includes setup for every tool you need. You do not need any prior coding experience.

Install Python

Every API you build in this project runs on Python. Python is a programming language that reads instructions you write in a file and executes them.

In this step, you'll install Python on your machine and confirm it's ready to use in your terminal.

In this step, get ready to:
  • Download and install Python 3.14.5.
  • Verify the installation works in your terminal.
Open the terminal and check for Python

The terminal is where you tell your computer to run instructions. You'll use it throughout this project to start your API and install tools.

  • In Antigravity IDE, press Ctrl+` to open the integrated terminal.
  • Check if Python is already on your machine by running this command:
python --version

Look at what appears in your terminal and pick the tab below that matches what you see.

✔️ I see version 3.14 or higher

Python is ready to go. You can move on to the next substep.

ⓧ I see an older version

You have an older version of Python installed. You need 3.14 or higher for this project. Follow the steps below to install the correct version.

  • Open your browser and go to https://www.python.org/downloads/.
  • Click Download Python 3.14.5 to download the Windows 64-bit installer.
  • Run the downloaded installer file.
  • On the first page of the installer, check the box labelled Add Python to PATH before doing anything else.
  • Click Install Now.
  • Once the installer finishes, close and reopen the terminal in Antigravity IDE by pressing Ctrl+` twice (once to close, once to reopen).
  • Run the version check again:
python --version

You should now see Python 3.14.5.

ⓧ Command not found

Python is not installed yet. Follow the steps below to install it.

  • Open your browser and go to https://www.python.org/downloads/.
  • Click Download Python 3.14.5 to download the Windows 64-bit installer.
  • Run the downloaded installer file.
  • On the first page of the installer, check the box labelled Add Python to PATH before doing anything else.
  • Click Install Now.
  • Once the installer finishes, close and reopen the terminal in Antigravity IDE by pressing Ctrl+` twice (once to close, once to reopen).
  • Run the version check:
python --version

You should now see Python 3.14.5.

Why does Add Python to PATH matter?

PATH is a list of folders your computer checks when you type a command. Without adding Python to PATH, your terminal won't know where to find the python command.

Checking this box during install saves you from manually configuring it later.

Still not seeing Python 3.14.5?

Make sure you closed and reopened the terminal after installing. The terminal only reads PATH changes when it starts fresh.

If you still see an older version, you may have a previous Python installation taking priority. Try running where python to see which Python executable is being found first.

Help me fix my Python version issue

Understand what you just installed

What is Python and what did that command do?

Python is a programming language. It reads instructions you write in a .py file and executes them line by line.

The terminal is where you tell your computer to run those instructions. When you typed python --version, you asked Python to report which version it is. This confirms that your computer can find and run Python successfully.

Python is installed and your terminal can find it. Next up, you'll create your project folder, set up an isolated environment, and install FastAPI.

Set Up Your Project and Install FastAPI

With Python installed, you need a dedicated workspace for your API project.

In this step, you'll create a project folder, set up an isolated Python environment, and install FastAPI. FastAPI is a framework that handles the heavy lifting of building APIs so you can focus on your application logic.

In this step, get ready to:
  • Create your project folder and a virtual environment.
  • Install FastAPI.
  • Create your main Python file.
Create your project folder and virtual environment

A virtual environment is an isolated Python workspace. It keeps your project's packages separate from other Python projects on your machine.

  • Move to your Desktop by running this command in your terminal:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir task-api
cd task-api

What do these commands do?

  • mkdir task-api creates a new folder called task-api on your Desktop.
  • cd task-api moves your terminal inside that folder so all future commands happen there.
  • Confirm the folder exists by running:
ls

You should see the task-api folder listed in the output.

  • Create and activate your virtual environment by running these commands:
python -m venv venv
venv\Scripts\activate

What do these commands do?

  • python -m venv venv creates an isolated Python environment inside a folder called venv.
  • venv\Scripts\activate turns on that environment so any packages you install stay contained to this project.
  • Check that your terminal now shows (venv) at the beginning of the line.

This prefix confirms the virtual environment is active.

Seeing a 'running scripts is disabled' error?

PowerShell may block the activation script due to its execution policy. Run this command to allow it for this session:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned

Then try the activate command again.

help me fix the execution policy error when activating my virtual environment

Install FastAPI

FastAPI is the framework that lets you build APIs without writing hundreds of lines of boilerplate code. You install it using pip, Python's package installer.

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

What does this command do?

  • pip is Python's package installer. Think of it like an app store for Python libraries.
  • fastapi[standard] installs FastAPI plus all the tools it needs to run a development server.
  • Wait for the installation to finish. You should see a Successfully installed message at the end of the output.

Installation failing?

  • Make sure you see (venv) in your terminal. If not, run venv\Scripts\activate first.
  • Check your internet connection. pip downloads packages from the internet.

help me troubleshoot my FastAPI installation

Create your main file

Your entire API will live in a single Python file. This keeps everything visible and easy to follow as you learn.

  • In Antigravity IDE, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the task-api folder, then click Open.

You should see the task-api folder appear in the Explorer sidebar on the left, with the venv folder inside it.

  • Hover over the TASK-API heading in the Explorer sidebar.
  • Click the New File icon (a page with a + symbol).
  • Type main.py and press Enter.

The file opens in the editor, ready for you to start writing code in the next step.

Why main.py?

The name main.py is a convention in Python projects. FastAPI's development server looks for this file by default when you start it up.

Your workspace is ready. Next up, you'll write your first API endpoint and see it respond in the browser.

Write Your First API Endpoint

Your project is set up and ready to go. Now it's time to write actual Python code that responds to web requests.

In this step, you'll create your first API endpoint and see it running live in your browser. You'll also discover how FastAPI automatically generates interactive documentation for your API.

In this step, get ready to:
  • Write Python code using variables, functions, and decorators.
  • Run your API server and see it respond in the browser.
  • Explore the auto-generated Swagger documentation.
Write your first API code

Every API needs code that listens for requests and sends back responses. You're about to write a small Python program that does exactly that.

  • In main.py, type the following code line by line (don't copy-paste):
from fastapi import FastAPI

# Create your API application
app = FastAPI()

# When someone visits the homepage (/), run this function
@app.get("/")
def hello():
    return {"message": "Hello World"}

What does this code do?

  • from fastapi import FastAPI gets the FastAPI tool from the library you installed in the previous step.
  • app = FastAPI() creates your API application. app is a variable (a name that holds something).
  • @app.get("/") is a decorator. It says "when someone visits the homepage (/), run the function below."
  • def hello(): is a function (a named block of code). def means "define" and hello is the name you chose.
  • return {"message": "Hello World"} sends back data. The curly braces {} make a dictionary (key-value pairs).
  • Save main.py by pressing Ctrl+S.

Seeing red squiggly lines?

If your editor shows errors under FastAPI, make sure your virtual environment is active. You should see (venv) at the beginning of your terminal prompt.

If (venv) is missing, re-activate it by running venv\Scripts\activate in the terminal.

Help me fix the red underlines in my main.py file

✔️ 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()

@app.get("/")
def hello():
    return {"message": "Hello World"}
Run your API

Your code is saved but nothing is happening yet. You need to start a server that listens for incoming requests and routes them to your function.

  • Start your API server by running this command in the terminal:
fastapi dev main.py

You should see output including Uvicorn running on http://127.0.0.1:8000 in the terminal.

What does this command do?

fastapi dev starts a development server that reads your main.py file, detects your FastAPI app, and starts listening for requests on port 8000.

The dev mode also watches for file changes. If you edit and save main.py, the server automatically reloads with your new code.

  • Open your browser and go to http://127.0.0.1:8000.
  • You should see {"message":"Hello World"} displayed in the browser.

Don't see the JSON response?

Make sure the server is still running in your terminal. If you closed it or pressed Ctrl+C, run fastapi dev main.py again.

Check that you typed the URL exactly as http://127.0.0.1:8000 with no trailing slash or extra characters.

Help me get my FastAPI server running

Explore the auto-generated docs

FastAPI automatically creates interactive documentation for every endpoint you build. This is one of the biggest advantages over other frameworks.

  • In your browser, go to http://127.0.0.1:8000/docs.
  • You should see the Swagger UI page with your GET / endpoint listed.
  • Click on the GET / endpoint to expand it.
  • Click Try it out.
  • Click Execute.
  • You should see the response body {"message":"Hello World"} appear below.

What is Swagger UI?

Swagger UI is an interactive documentation page that FastAPI generates from your code. Front-end developers use this to understand what endpoints are available, what data they accept, and what they return.

This is what the job description means by "write API document for better communication with front end developers." You didn't write any documentation. FastAPI created it from your code automatically.

You now have a working API with auto-generated documentation. Next up, you'll create a data model that defines what a "task" looks like so your API can validate incoming data automatically.

Create the Task Data Model

In the last step, you got your API up and running with a Hello World endpoint. Now it's time to teach your API what a task actually looks like.

To manage tasks, your API needs a blueprint that defines the required properties of a task. In Python, you create these blueprints using classes. FastAPI pairs with a library called Pydantic to automatically validate incoming data against your blueprint.

In this step, get ready to:
  • Define a Task data model with built-in validation.
  • Create in-memory storage for your tasks.
Define the Task data model

A class is a blueprint that describes what properties something has. Your Task class tells FastAPI exactly what fields a task needs and what type of data each field accepts.

  • In main.py, find the line from fastapi import FastAPI at the top of your file.
  • Add this new import directly below it:
from pydantic import BaseModel
  • Now find the line app = FastAPI(). Add the Task class below it by typing the following code:
class Task(BaseModel):
    # title is required - must be a text string
    title: str
    # description is optional - defaults to nothing
    description: str | None = None
    # status defaults to "pending" if not provided
    status: str = "pending"

What does this code do?

from pydantic import BaseModel imports the BaseModel tool from Pydantic. This is what gives your class the ability to validate data automatically.

class Task(BaseModel): creates a blueprint called Task. The (BaseModel) part means Task inherits all of Pydantic's validation powers.

  • title: str means the title must be text and is required (no default value).
  • description: str | None = None means the description is optional. None means empty or nothing.
  • status: str = "pending" means status defaults to "pending" if the user doesn't provide one.
Create in-memory storage

Your API needs somewhere to keep tasks after they're created. In a real job you'd use a database, but for learning purposes a Python list works the same way conceptually.

  • Below the Task class (after the last line status: str = "pending"), add these two variables:
# Store tasks in a list (acts as our database)
tasks = []
# Give each task a unique ID number
task_id_counter = 0

What do these variables do?

  • tasks = [] creates an empty list. Each time you create a task, it gets added to this list.
  • task_id_counter = 0 is a counter that gives each new task a unique number. The first task gets ID 1, the second gets ID 2, and so on.
  • Save main.py.

You won't see a visible change yet. The next step uses this model to create tasks through a POST endpoint.

Seeing red squiggly lines in your editor?

  • Make sure from pydantic import BaseModel is on its own line directly below from fastapi import FastAPI. Both import lines must be at the top of the file.
  • Check that class Task(BaseModel): uses a capital T in Task and a capital B and M in BaseModel. Python is case-sensitive.
  • Confirm your virtual environment is still active. You should see (venv) in your terminal. If not, run venv\Scripts\activate again.

Still stuck? Help me fix the red squiggly lines in my Task data model code.

Understand the data flow

How does validation work?

When someone sends data to your API, FastAPI checks it against the Task blueprint automatically. If the data matches (has a title, correct types), it gets processed. If it doesn't match, FastAPI returns an error explaining what's wrong.

For example, if someone tries to create a task without a title, FastAPI rejects it immediately. You wrote zero error-handling code for this. That's the power of data models.

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py before moving on.

ⓧ 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
from pydantic import BaseModel

app = FastAPI()

class Task(BaseModel):
    title: str
    description: str | None = None
    status: str = "pending"

tasks = []
task_id_counter = 0

@app.get("/")
def hello():
    return {"message": "Hello World"}

Your data model is locked in. Next up, you'll build the POST endpoint that actually creates tasks and stores them in your list.

Build the POST Endpoint

Your API has a data model that defines what a task looks like. Now it's time to give users a way to actually create tasks.

HTTP has different methods (also called verbs) that describe what action you want to perform on data. In this step, you'll use the POST method to build an endpoint that accepts new task data and stores it in your list.

In this step, get ready to:
  • Learn what HTTP methods are and what POST does.
  • Write the POST /tasks endpoint.
  • Test your endpoint in Swagger UI.
Learn what POST is and add the endpoint

HTTP methods tell the server what you want to do with data. Think of them as verbs for your API.

What are HTTP methods?

  • GET = "give me data" (read).
  • POST = "here's new data, save it" (create).
  • PUT = "here's updated data" (update).
  • DELETE = "remove this data" (delete).

These four methods map directly to CRUD operations (Create, Read, Update, Delete). You already built a GET endpoint for your homepage. Now you'll build a POST endpoint that creates tasks.

  • In main.py, find the hello() function at the bottom of your file.
  • Add the following code below it to create your POST endpoint:
@app.post("/tasks", status_code=201)
def create_task(task: Task):
    # Use the counter from outside this function
    global task_id_counter
    # Give this task a unique ID
    task_id_counter += 1
    # Convert the task object to a dictionary
    task_data = task.model_dump()
    # Add the ID to the dictionary
    task_data["id"] = task_id_counter
    # Store the task in our list
    tasks.append(task_data)
    # Send the created task back
    return task_data

What does this code do?

  • @app.post("/tasks", status_code=201) is a decorator. It says "when someone sends a POST request to /tasks, run the function below." Status code 201 means "created successfully."
  • def create_task(task: Task) defines a function that receives a task parameter. The : Task part tells FastAPI to validate incoming data against your Task model.
  • global task_id_counter lets the function access and modify the counter variable you defined outside.
  • task_id_counter += 1 adds 1 to the counter. The first task gets ID 1, the second gets ID 2, and so on.
  • task.model_dump() converts the task object into a dictionary (key-value pairs) so you can add more data to it.
  • task_data["id"] = task_id_counter adds the unique ID to the task dictionary.
  • tasks.append(task_data) adds the task to your list. In a real job, this would save to a database instead.
  • return task_data sends the created task (with its new ID) back to whoever made the request.
  • Save main.py.

Seeing red underlines in your editor?

Make sure the new code is outside and below the hello() function, not indented inside it. Each @app decorator should start at the very left edge of the file with no spaces before it.

Also check that your indentation uses spaces, not tabs. Python is strict about consistent indentation.

help me fix indentation errors in my FastAPI code

Test your POST endpoint in Swagger UI

Your endpoint is written. Time to start the server and see it working live in your browser.

  • Start your API server by running this command in the terminal:
fastapi dev main.py

You should see output that includes "Server started at http://127.0.0.1:8000" and "Application startup complete."

Seeing an error instead of the server starting?

Make sure your virtual environment is active. You should see (venv) at the start of your terminal line. If not, run venv\Scripts\activate first.

If you see a syntax error, double check that all parentheses and brackets in your code are closed properly.

help me fix the error when running fastapi dev main.py

  • Open your browser and go to http://127.0.0.1:8000/docs.
  • Click on the green POST /tasks endpoint to expand it.
  • Click Try it out.
  • In the Request body field, replace the example content with this:
{"title": "Learn Python", "description": "Understand variables and functions"}
  • Click Execute.

You should see a response with your task returned, including an id of 1 and a status of "pending". You never sent a status value, but your Task model set the default for you.

Why did the status appear automatically?

Remember the Task model you created earlier with status: str = "pending"? The = "pending" part is a default value. If the person making the request doesn't include a status, FastAPI fills it in automatically.

This is the power of data models. They handle defaults and validation without you writing extra code.

Not seeing the POST endpoint in docs?

Make sure you saved main.py after adding the new code. The dev server auto-reloads when you save, but unsaved changes won't appear.

If the endpoint appears but returns a 422 error, check that your JSON is valid. The title field is required because it has no default value in your Task model.

help me troubleshoot my POST endpoint not working in Swagger UI

✔️ 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
from pydantic import BaseModel

app = FastAPI()

class Task(BaseModel):
    title: str
    description: str | None = None
    status: str = "pending"

tasks = []
task_id_counter = 0

@app.get("/")
def hello():
    return {"message": "Hello World"}

@app.post("/tasks", status_code=201)
def create_task(task: Task):
    global task_id_counter
    task_id_counter += 1
    task_data = task.model_dump()
    task_data["id"] = task_id_counter
    tasks.append(task_data)
    return task_data

You just built your first endpoint that writes data. Your API can now create tasks and return them with unique IDs. Next up, you'll build the GET endpoints so you can retrieve those tasks back.

Build the GET Endpoints

You can now create tasks through your POST endpoint. The next question is: how do you get that data back out?

In this step, you'll build two GET endpoints. One returns every task in your list. The other returns a single task by its ID. Together with POST, these give your API the "Read" part of CRUD.

In this step, get ready to:
  • Add a GET endpoint that returns all tasks.
  • Add a GET endpoint that returns a single task by its ID.
  • Test both endpoints through Swagger UI.
Add the GET all tasks endpoint

Your first GET endpoint is the simplest one possible. It returns the entire tasks list whenever someone sends a GET request to /tasks.

  • In main.py, scroll to the bottom of the file (below the create_task function) and add this code:
@app.get("/tasks")
def get_tasks():
    return tasks

What does this code do?

This is a complete endpoint in just three lines:

  • @app.get("/tasks") tells FastAPI: when someone sends a GET request to /tasks, run the function below.
  • def get_tasks(): defines a function with no parameters because listing all tasks doesn't need any input.
  • return tasks sends back the entire list. FastAPI automatically converts it to JSON.
  • Save main.py.
  • Your server auto-reloads when you save. Switch to your browser and refresh http://127.0.0.1:8000/docs. You should see a new GET /tasks endpoint in the list.

Don't see the new endpoint?

  • Make sure you saved the file. The dev server only reloads when it detects a file change on disk.
  • Check that your new code is outside the create_task function. It should not be indented under create_task.
  • Look at your terminal for error messages. A typo in the decorator or function name will prevent the reload from succeeding.

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

Add the GET single task endpoint

Returning all tasks is useful, but you also need a way to fetch one specific task. This endpoint uses a path parameter to let the caller specify which task they want by its ID.

  • In main.py, add this code below the get_tasks function you just wrote:
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
    for task in tasks:
        if task["id"] == task_id:
            return task
    return {"error": "Task not found"}

What does this code do?

This endpoint introduces two new concepts: path parameters and loops.

  • {task_id} in the path is a path parameter. The URL /tasks/1 means task_id equals 1.
  • task_id: int tells FastAPI that this value must be a number. If someone passes text instead, FastAPI automatically returns an error.
  • for task in tasks: is a loop. It goes through each task in the list one by one.
  • if task["id"] == task_id: checks whether the current task's ID matches the one requested.
  • return {"error": "Task not found"} runs only if the loop finishes without finding a match. This handles the case where someone requests an ID that doesn't exist.
  • Save main.py.
  • Refresh http://127.0.0.1:8000/docs in your browser. You should see a new GET /tasks/{task_id} endpoint appear below GET /tasks.

Seeing a server error in the terminal?

  • Check that you have matching quotes around "id" and "error". Missing a quote will cause a syntax error.
  • Make sure the if line is indented one level inside the for loop, and the return task line is indented one level inside the if block.
  • Confirm you typed task_id: int with a colon and a space. This type annotation is required for FastAPI to validate the parameter.

Help me fix a syntax error in my GET /tasks/{task_id} endpoint.

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py and both new endpoints appear in the Swagger docs.

ⓧ I'd like to double check the full code

Here is the complete main.py at this point in the project:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Task(BaseModel):
    title: str
    description: str | None = None
    status: str = "pending"

tasks = []
task_id_counter = 0

@app.get("/")
def hello():
    return {"message": "Hello World"}

@app.post("/tasks", status_code=201)
def create_task(task: Task):
    global task_id_counter
    task_id_counter += 1
    task_data = task.model_dump()
    task_data["id"] = task_id_counter
    tasks.append(task_data)
    return task_data

@app.get("/tasks")
def get_tasks():
    return tasks

@app.get("/tasks/{task_id}")
def get_task(task_id: int):
    for task in tasks:
        if task["id"] == task_id:
            return task
    return {"error": "Task not found"}
Test both endpoints in Swagger UI

Your GET endpoints are live. Time to confirm they work by creating some tasks and reading them back.

  • In Swagger UI at http://127.0.0.1:8000/docs, click on POST /tasks.
  • Click Try it out.
  • In the Request body field, type:
{"title": "Learn Python", "description": "Understand variables and functions"}
  • Click Execute.
  • Create a second task by changing the Request body to:
{"title": "Build an API", "description": "Create GET and POST endpoints"}
  • Click Execute again.
  • Now click on GET /tasks, then Try it out, then Execute.

You should see both tasks returned as a JSON list.

  • Click on GET /tasks/{task_id}, then Try it out.
  • Enter 1 in the task_id field and click Execute. You should see just the first task.
  • Now enter 999 in the task_id field and click Execute. You should see {"error": "Task not found"}.

Why test with a non-existent ID?

APIs need to handle bad input gracefully. When a frontend developer requests a task that doesn't exist, your API returns a clear error message instead of crashing. This is what makes your API reliable.

You now have the full "Read" side of your API working. Next up, you'll build PUT and DELETE endpoints to complete the CRUD cycle.

Build the PUT and DELETE Endpoints

Your API can create and read tasks. That covers half of what a backend developer does daily.

The other half is updating and deleting data. In this step, you'll add PUT and DELETE endpoints to complete your CRUD API.

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

PUT is the HTTP method that means "here's updated data, replace what you have." Your PUT endpoint will find a task by its ID, swap in the new data, and return the updated task.

This endpoint needs a new Python tool called enumerate. It loops through a list and gives you both the item AND its position number (index). You need the position so you can replace the old task at that exact spot.

  • In main.py, add this code at the very bottom of the file, after your get_task function:
@app.put("/tasks/{task_id}")
def update_task(task_id: int, updated_task: Task):
    # Loop through tasks with their position numbers
    for index, task in enumerate(tasks):
        if task["id"] == task_id:
            # Convert updated task to dictionary
            task_data = updated_task.model_dump()
            # Keep the same ID
            task_data["id"] = task_id
            # Replace the old task at this position
            tasks[index] = task_data
            return task_data
    return {"error": "Task not found"}

What does this code do?

  • @app.put("/tasks/{task_id}", ...) tells FastAPI: when someone sends a PUT request to /tasks/1, run this function.
  • The function takes two inputs: task_id (which task to update) and updated_task (the new data, validated against your Task model).
  • enumerate(tasks) gives you both the index (0, 1, 2...) and the task at each position. You need the index to replace the task in the list.
  • tasks[index] = task_data replaces the old task at that exact position with the new data.
  • Save main.py.
  • Your server auto-reloads when you save. In your browser, go to http://127.0.0.1:8000/docs. You should see a new green PUT /tasks/{task_id} endpoint in the list.

Don't see the PUT endpoint?

  • Make sure you saved main.py. Unsaved changes won't trigger the auto-reload.
  • Check your terminal for error messages. A typo in the code will prevent the server from reloading.
  • Make sure the new code is outside (below) the get_task function, not inside it.

Why isn't my PUT endpoint showing up in the docs?

Add the DELETE endpoint

DELETE is the HTTP method that means "remove this data." Your DELETE endpoint finds a task by its ID and removes it from the list entirely.

  • In main.py, add this code below your update_task function:
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
    # Loop through tasks with their position numbers
    for index, task in enumerate(tasks):
        if task["id"] == task_id:
            # Remove the task at this position
            tasks.pop(index)
            return {"message": "Task deleted"}
    return {"error": "Task not found"}

What does this code do?

  • @app.delete("/tasks/{task_id}") tells FastAPI: when someone sends a DELETE request to this path, run this function.
  • tasks.pop(index) removes the item at that position from the list. The task is gone permanently.
  • If no task matches the ID, the function returns an error message instead.
  • Save main.py.
  • Refresh http://127.0.0.1:8000/docs in your browser. You should now see a red DELETE /tasks/{task_id} endpoint in the list.

Seeing an error in the terminal?

  • Check that your indentation is consistent. Python uses spaces (not tabs) to define code blocks. Every line inside the function must be indented the same amount.
  • Make sure tasks.pop(index) is inside the if block (indented further than the for loop).

Help me fix my DELETE endpoint code.

✔️ Awesome, I've got everything!

Great. Make sure you've saved your file before moving on to test the full CRUD cycle.

ⓧ 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
from pydantic import BaseModel

app = FastAPI()

class Task(BaseModel):
    title: str
    description: str | None = None
    status: str = "pending"

tasks = []
task_id_counter = 0

@app.get("/")
def hello():
    return {"message": "Hello World"}

@app.post("/tasks", status_code=201)
def create_task(task: Task):
    global task_id_counter
    task_id_counter += 1
    task_data = task.model_dump()
    task_data["id"] = task_id_counter
    tasks.append(task_data)
    return task_data

@app.get("/tasks")
def get_tasks():
    return tasks

@app.get("/tasks/{task_id}")
def get_task(task_id: int):
    for task in tasks:
        if task["id"] == task_id:
            return task
    return {"error": "Task not found"}

@app.put("/tasks/{task_id}")
def update_task(task_id: int, updated_task: Task):
    for index, task in enumerate(tasks):
        if task["id"] == task_id:
            task_data = updated_task.model_dump()
            task_data["id"] = task_id
            tasks[index] = task_data
            return task_data
    return {"error": "Task not found"}

@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
    for index, task in enumerate(tasks):
        if task["id"] == task_id:
            tasks.pop(index)
            return {"message": "Task deleted"}
    return {"error": "Task not found"}
Test the full CRUD cycle

You now have all four CRUD operations. Let's test them together to prove everything works end-to-end.

  • In Swagger UI at http://127.0.0.1:8000/docs, click POST /tasks.
  • Click Try it out.
  • In the Request body, type:
{"title": "Learn Python", "description": "Understand variables and functions"}
  • Click Execute. You should see the task returned with an ID of 1 and status "pending".
  • Now click GET /tasks/{task_id}.
  • Click Try it out, enter 1 for the task_id, and click Execute. You should see your task returned.
  • Click PUT /tasks/{task_id}.
  • Click Try it out, enter 1 for the task_id, and paste this into the Request body:
{"title": "Learn Python", "description": "Understand variables and functions", "status": "done"}
  • Click Execute. The response should show your task with "status": "done".
  • Click DELETE /tasks/{task_id}.
  • Click Try it out, enter 1 for the task_id, and click Execute. You should see {"message": "Task deleted"}.
  • Finally, click GET /tasks, click Try it out, and click Execute. You should see an empty list [] confirming the task is gone.

What just happened?

You just completed a full CRUD cycle: Create (POST), Read (GET), Update (PUT), Delete (DELETE). This is exactly what backend developers do every day when building APIs for web and mobile apps.

Getting "Task not found" when updating or deleting?

  • Your task data resets every time the server reloads. If you saved main.py between creating a task and testing PUT/DELETE, the tasks list is empty again. Create a new task with POST first, then test.
  • Make sure the task_id you enter matches the ID returned when you created the task.

Help me figure out why I'm getting Task not found.

You now have a complete REST API with full CRUD operations. This is exactly what backend developers build every day.

Secret mission

Add a Filter Endpoint

You've built a complete CRUD API. Now challenge yourself: add an endpoint that lets users filter tasks by status. This is a common real-world feature. Think about how you'd let someone ask "show me only the tasks that are done."

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:

  • task-api project folder (includes virtual environment, main.py, and installed packages).
  • Python 3.14.5 (installed on your system).

Keep everything running

No action needed. Choose this if you're still actively building or plan to add more endpoints to your API.

  • Your FastAPI dev server continues running at http://127.0.0.1:8000.
  • Your virtual environment stays active in the terminal.
  • Python remains installed and available for other projects.

Pause - I'll come back to this later

Stop the running server and deactivate your virtual environment to free up terminal resources. Your files stay intact for when you return.

  • In your terminal, press Ctrl+C to stop the FastAPI dev server.
  • Type deactivate and press Enter to exit the virtual environment.

When you come back, reactivate your environment by running:

cd task-api
venv\Scripts\activate
fastapi dev main.py

This puts you right back where you left off.

Delete - I don't want to use this again

Remove all project resources and start fresh. This cannot be undone.

  • In your terminal, press Ctrl+C to stop the FastAPI dev server (if still running).
  • Type deactivate and press Enter to exit the virtual environment.
  • Navigate up one level out of the project folder by running:
cd ..
  • Delete the task-api folder by running:
rmdir /s /q task-api
  • To uninstall Python, press the Windows key and search for Settings.
  • Go to Apps, then Installed apps.
  • Find Python 3.14.5 in the list, click the three dots icon, and select Uninstall.
  • Confirm by clicking Uninstall again when prompted.

Nice Work!

Nice Work!

You just built a fully functional REST API from scratch. You typed every line, understood every concept, and now have a working Task Manager API running locally with interactive documentation.

You've learned how to:

  • Build a complete CRUD REST API with all four HTTP methods (GET, POST, PUT, DELETE) using Python and FastAPI.
  • Validate incoming data automatically with Pydantic models, ensuring every task has the right structure before it's processed.
  • Generate interactive API documentation (Swagger UI) that front-end developers can use to understand and test your endpoints.
  • Add a filter endpoint using query parameters to search tasks by status.

Ready to quiz yourself?