Build a Task Tracker API with FastAPI

Build a validated FastAPI task API with CRUD routes and Swagger UI.

Introduction

30 Second Summary

A task list becomes more useful when other tools can manage its items. Those tools need a predictable way to exchange data.

In this project, you will build a local Task Tracker REST API with FastAPI. You will add Pydantic validation before demonstrating CRUD behavior through Swagger UI.

What You'll Build

You'll use interactive API documentation in your browser to take a task through its full lifecycle while each response appears on screen.

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

  • A live JSON health check that confirms your local server is ready to receive requests.
  • An interactive task workflow for listing tasks and creating new ones through the browser.
  • A validated task lifecycle that rejects malformed data and supports retrieval, replacement, and deletion by ID.
  • Secret Mission: Add an optional completion filter to the task list endpoint.

Are there any prerequisites?

You should be comfortable with Python functions, lists, classes, and type hints. You'll also need Python 3.10 or newer, Visual Studio Code, macOS Terminal, and a web browser.

Before We Start

This is your moment to commit to building a local Task Tracker REST API with FastAPI. You will focus on REST API fundamentals through visible in-memory behavior.

Set Up the Local Project

Your Task Tracker REST API needs a predictable home before you add routes. An isolated project keeps its packages from colliding with other Python work.

In this step, you create the task-tracker-api folder in Visual Studio Code. A virtual environment will hold pinned versions of FastAPI plus Pydantic.

In this step, get ready to:
  • Create the task-tracker-api folder with two starter files.
  • Pin the project's exact dependency versions.
  • Build an isolated Python environment with the project dependencies installed.
Create the project folder and files

The project folder keeps your API code beside the dependency list that recreates its environment. You will store it on your Desktop so it stays easy to find.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type Visual Studio Code into the search bar.
  • Press Enter to launch Visual Studio Code.

Keep the Project Together

Opening one named folder gives Visual Studio Code a clear boundary for your files. The integrated terminal also starts inside that folder.

  • Click File in the top menu bar.
  • Click Open Folder.
  • Select Desktop in the folder picker.
  • Click New Folder.
  • Enter task-tracker-api as the folder name.
  • Click Create.
  • Click Open to load the new folder in Visual Studio Code.

Good start. You should see task-tracker-api at the top of the Explorer sidebar.

  • Click the New File icon in the Explorer sidebar.
  • Enter main.py as the file name.
  • Press Enter to create the file.
  • Click the New File icon again.
  • Enter requirements.txt as the file name.
  • Press Enter to create the file.

You should see main.py plus requirements.txt in the Explorer sidebar. The main.py file remains empty for now.

Pin the project dependencies

A pinned dependency records the exact package release this project expects. Anyone rebuilding the environment receives the same FastAPI and Pydantic versions.

  • Select requirements.txt in the Explorer sidebar.
  • Paste the dependency pins below into requirements.txt.
fastapi[standard]==0.141.1
pydantic==2.13.5

What Do These Pins Do?

  • The fastapi[standard]==0.141.1 pin installs FastAPI 0.141.1 with its standard extras.
  • The pydantic==2.13.5 pin installs Pydantic 2.13.5.
  • Exact pins keep future package releases from changing this project's behavior.
  • Press Cmd+S on macOS or Ctrl+S on Windows to save requirements.txt.
  • Confirm requirements.txt contains exactly two non-empty lines.

Do the Dependency Lines Look Different?

Check every bracket plus equals sign against the reference below. Package installers treat a missing character as a different requirement.

Ask for help with the comparison: Why does my requirements.txt file fail to match these FastAPI and Pydantic pins?

✔️ Awesome, I've got everything!

Your two starter files now match the project checkpoint.

ⓧ I'd like to double check the full code

Your main.py file should be completely empty at this stage.

fastapi[standard]==0.141.1
pydantic==2.13.5

Why FastAPI for This Project?

FastAPI keeps your attention on API behavior because request handling plus interactive documentation fit into the main workflow.

The equivalent setup in Flask requires extra validation choices.

An Express project introduces JavaScript plus separate validation setup.

Create and activate the virtual environment

The virtual environment gives this project its own package directory. Installing dependencies there protects your other Python projects from version conflicts.

  • Click Terminal in the Visual Studio Code menu bar.
  • Click New Terminal.

You should see an integrated terminal with task-tracker-api as its current folder.

  • Create the isolated environment by running this command:
python3 -m venv .venv

What Does This Command Do?

  • The python3 command starts your available Python 3 interpreter.
  • The venv module creates the isolated environment.
  • The .venv value names the environment folder inside task-tracker-api.
  • Wait for the terminal prompt to return.
  • Confirm .venv appears in the Explorer sidebar.

Did the Environment Fail to Appear?

Confirm the terminal path ends with task-tracker-api. A different path creates the environment in the wrong folder.

Get help with the environment command: Why did Python fail to create my .venv folder?

Before you activate the environment, what version do you expect its Python interpreter to report?

  • Activate the environment plus check its Python version by running these commands:
source .venv/bin/activate
python --version

What Do These Commands Check?

  • The source .venv/bin/activate command switches the current terminal session into the project environment.
  • The python --version command reports the interpreter used by that activated environment.

You should see Python 3.10 or newer. Your terminal may also show (.venv) at the start of its prompt.

Seeing an Older Version or No Python?

Removing this new .venv folder is safe. It contains only the disposable environment you just created.

  • Download a current installer from the Python Releases for macOS page.
  • Run the macOS installer with its default options.
  • Delete .venv from the Visual Studio Code Explorer sidebar.
  • Restart Visual Studio Code to refresh its Python path.
  • Repeat the environment creation plus activation commands above.

Get help choosing the correct interpreter: Why is my virtual environment using an older Python version?

Before you install the dependencies, what should the package installer read from requirements.txt?

  • Install the pinned dependencies by running this command:
python -m pip install -r requirements.txt

What Does This Install Command Do?

  • The python -m pip section runs the package installer through the activated Python interpreter.
  • The -r requirements.txt section tells the installer to process every line in the dependency file.

The command should finish without an installation error. That's the setup done. Your isolated environment now contains the pinned project dependencies.

Did the Dependency Installation Fail?

  • Confirm the terminal prompt still shows the active .venv environment.
  • Confirm the terminal is inside the task-tracker-api folder.
  • Check that requirements.txt matches the two-line reference exactly.

Get help with the failed installation: Why did pip fail to install my pinned FastAPI dependencies?

Your local project is isolated plus reproducible. Next, you will turn the empty main.py file into a running JSON endpoint.

Launch Your First JSON Endpoint

Your isolated Python environment is ready for the Task Tracker API. The empty main.py file cannot answer an HTTP request yet.

In this step, you will connect a Python function to a URL with FastAPI. A visible health response proves that the function is reachable before the API becomes more complex.

In this step, get ready to:
  • Define the FastAPI application in main.py.
  • Launch the development server.
  • Verify the health route through the browser.
Build the health route

A route pairs one HTTP method with one URL path. FastAPI runs root() whenever a client sends a request to GET /.

  • In Visual Studio Code, select main.py from the Explorer sidebar.
  • Fill the empty file by pasting this code:
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {"message": "Task Tracker API is running"}

What does this code do?

  • The FastAPI import brings the application class into main.py.
  • The app object represents your API.
  • The @app.get("/") decorator connects the root URL to root().
  • The returned dictionary becomes a JSON response automatically.
  • Save main.py.

Before you start the server, do you expect FastAPI to discover the app object in main.py automatically?

  • Start the development server from the activated terminal by running:
fastapi dev

What does this command do?

The FastAPI CLI detects the app object in main.py. It starts development mode at http://127.0.0.1:8000.

The development process stays active while it serves requests.

  • Keep the development server running for the browser checks.

You will see terminal output showing that the development server is available at http://127.0.0.1:8000.

Development server not starting?

  • Confirm the virtual environment from the previous step is still active in the terminal.
  • Check that main.py sits inside the task-tracker-api folder.
  • Check that your file defines app = FastAPI() exactly as shown.

Help me troubleshoot the development server.

✔️ Awesome, I've got everything!

Your saved main.py now matches the working health endpoint.

ⓧ I'd like to double check the full code

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {"message": "Task Tracker API is running"}

How to use this reference

This reference is the complete main.py file for this step. Its blank lines separate the application setup from the route definition.

Check the JSON response

When the route returns a Python dictionary, FastAPI serializes it into JSON. The browser shows the response body sent by your server.

Before you load the address, what response do you expect the dictionary in root() to become?

  • Enter http://127.0.0.1:8000/ in your browser's address bar.
  • Press Enter.

You will see {"message":"Task Tracker API is running"} in the browser.

That is your first live API response. Your Python function is now reachable through an HTTP route.

Health response not loading?

  • Confirm the development server process is still running in the terminal.
  • Check that the browser address matches http://127.0.0.1:8000/ exactly.
  • Save main.py if Visual Studio Code still shows unsaved changes.

Help me troubleshoot the missing health response.

Inspect the generated API documentation

FastAPI uses your route definition to generate OpenAPI documentation. Swagger UI turns that documentation into an interactive browser interface.

Before you load the documentation, which method and path do you expect to find there?

  • Enter http://127.0.0.1:8000/docs in your browser's address bar.
  • Press Enter.
  • Locate the GET / operation in the route list.

You will see the GET / operation documented in Swagger UI. This proves FastAPI included your route in the generated documentation.

Your first endpoint now explains itself in the browser. That automatic documentation gives you a demo surface for every route you add next.

Documentation page not appearing?

  • Confirm the documentation URL ends with /docs.
  • Return to the root URL to confirm the server still responds.
  • Restart the development server if its terminal process has stopped.

Help me troubleshoot the missing Swagger UI page.

Your server now exposes a documented health route. Next, you will build the task collection behind the API.

Add Task Routes

Your FastAPI health endpoint proves that the server can answer a request. Your task tracker still needs a collection that clients can read and extend.

This step adds routes for listing tasks and creating tasks. You will probe the create route with different JSON bodies to discover how much structure a plain Python dictionary enforces.

In this step, get ready to:
  • Store a starter task in memory.
  • Expose the task collection through GET /tasks.
  • Create tasks through POST /tasks.
Store and list tasks

An in-memory list gives the API somewhere to keep tasks while the server is running. Each dictionary represents one task with an ID, title, and completion state.

  • In main.py, find the blank line below app = FastAPI().
  • Add the starter task collection before the @app.get("/") decorator by pasting this code:
tasks = [
    {"id": 1, "title": "Learn REST basics", "completed": False},
]
next_id = 2

What Does This Code Do?

  • The tasks list holds the task dictionaries that your routes can return.
  • The starter dictionary gives you one record to retrieve immediately.
  • The next_id value reserves the next numeric ID for a newly created task.
  • Save main.py.

The development server reloads the file. You should see it continue running without a Python error.

Server Reports a Python Error?

  • Check that the opening and closing square brackets around tasks both remain in place.
  • Confirm that False starts with a capital letter because this file contains Python.

Help me find the syntax problem in my starter task collection.

A route connects an HTTP method and URL path to a Python function. The listing route returns the current collection whenever a client sends a GET request.

  • In main.py, scroll below the root function.
  • Add the task listing route by pasting this code:
@app.get("/tasks")
def list_tasks():
    return tasks

How Does This Route Work?

  • The @app.get("/tasks") decorator connects GET requests at /tasks to the function below it.
  • The list_tasks function returns the current in-memory collection.
  • FastAPI converts the Python list into a JSON response for the client.
  • Save main.py.
  • Return to Swagger UI at http://127.0.0.1:8000/docs.
  • Refresh the browser page.

You will see the new GET /tasks operation. That is your first task collection route ready for requests.

  • Expand GET /tasks.
  • Click Try it out.
  • Click Execute.

You will see a successful response containing the starter task with ID 1.

Task Route Missing From Swagger UI?

  • Confirm that you saved main.py after adding the route.
  • Check that the decorator sits directly above the list_tasks function.
  • Refresh Swagger UI after the development server finishes reloading.

Help me diagnose why my task listing route is missing from Swagger UI.

Create tasks with POST

A POST route receives a request body from the client. This version accepts that body as a plain dict before assigning an ID and storing it.

  • In main.py, place the cursor below the list_tasks function.
  • Add the task creation route by pasting this code:
@app.post("/tasks", status_code=201)
def create_task(task: dict):
    global next_id
    task["id"] = next_id
    tasks.append(task)
    next_id += 1
    return task

What Does This Route Do?

  • The @app.post("/tasks", status_code=201) decorator exposes the creation route and declares the Created status code.
  • The global next_id line lets the function update the counter defined outside the function.
  • The route assigns the current ID before appending the dictionary to tasks.
  • The counter increases after each created task so the next record receives a different ID.
  • Save main.py.
  • Return to Swagger UI.
  • Refresh the browser page.

You will see POST /tasks beside the listing route. Your API can now receive task data.

POST Route Missing or Failing?

  • Confirm that create_task is aligned at the left edge of the file.
  • Check that every line inside create_task uses the same indentation.
  • Verify that the route decorator contains status_code=201.

Help me debug why my POST task route does not load correctly.

✔️ Awesome, I've got everything!

Your listing and creation routes are now in place. The next tab contains the complete file for comparison.

ⓧ I'd like to double check the full code

from fastapi import FastAPI

app = FastAPI()


tasks = [
    {"id": 1, "title": "Learn REST basics", "completed": False},
]
next_id = 2


@app.get("/")
def root():
    return {"message": "Task Tracker API is running"}


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


@app.post("/tasks", status_code=201)
def create_task(task: dict):
    global next_id
    task["id"] = next_id
    tasks.append(task)
    next_id += 1
    return task
Test what the route accepts

A request body carries the JSON data sent to a route. Two requests will show which body shapes the current route accepts.

  • Expand POST /tasks in Swagger UI.
  • Click Try it out.
  • Replace the request body with this valid task:
{
  "title": "Review REST methods",
  "completed": false
}

What Does This Request Send?

  • The title field describes the work to track.
  • The completed field records whether the work is finished.
  • The server supplies the numeric ID after receiving the request.
  • Click Execute.

You will see status code 201 with the task in the response. Your first created task now has a server-assigned ID.

Valid Task Request Failing?

  • Check that each JSON property name uses double quotes.
  • Confirm that false is lowercase in the JSON request body.
  • Remove any trailing comma after the final property.

Help me correct the JSON body for my valid task request.

Before you execute the next request, do you think a task without a title will be accepted?

  • Replace the request body with this empty JSON object:
{}

What Does This Request Send?

This request contains no task fields. It gives the route the smallest possible JSON object to process.

  • Click Execute.

You will see status code 201 again. The response contains a generated ID even though the request had no title.

The Empty Task Was Accepted

This is the intended shortfall. A plain dict accepts any JSON object because it does not define required fields.

The route successfully stores data that does not have the shape your task tracker needs.

Before you list the tasks again, what fields do you expect the newest object to contain?

  • Return to GET /tasks.
  • Expand the operation.
  • Click Execute.

You will see the malformed object stored in the list with an ID but no title. You have now exposed the request validation gap directly.

Malformed Task Missing From the List?

  • Confirm that the empty POST request returned status code 201 before listing the tasks.
  • Repeat the empty POST request if the development server restarted because in-memory tasks reset on restart.
  • Execute GET /tasks after the POST request completes.

Help me trace why the malformed task does not appear in my task list.

Your API can now list and create tasks, but it trusts every object it receives. Next, you will define the required task shape so incomplete requests are rejected before they reach the route.

Validate Task Requests

Your Swagger UI test exposed a serious gap. The API accepted an empty JSON object as a task.

Pydantic models define the required shape before a route processes data. In this step, FastAPI uses those models to reject incomplete tasks automatically.

In this step, get ready to:
  • Define models for incoming task data and stored task records.
  • Convert the starter record into a validated task object.
  • Reject invalid request bodies while preserving successful task creation.
Define the task schemas

A schema describes which fields a request may contain. It also defines the rules each field must satisfy.

  • In main.py, select the import section plus the app = FastAPI() line.
  • Replace the selection with this code:
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=100)
    completed: bool = False


class Task(TaskCreate):
    id: int

How Do These Models Work?

  • TaskCreate inherits from BaseModel. This gives Pydantic a model it can validate.
  • Field requires each title to contain between 1 and 100 characters.
  • completed defaults to False when a request omits it.
  • Task extends the request model with an integer id for stored records.
  • Save main.py.
  • Switch back to the terminal from earlier.
  • Check the server output for a completed reload with no Python traceback.

Good progress. Your server now loads both schema classes successfully.

Server Failing to Reload?

  • Confirm the Pydantic import sits directly below the FastAPI import.
  • Check that TaskCreate inherits from BaseModel.
  • Check that Task inherits from TaskCreate.

Help me fix my Pydantic model setup.

Store typed task objects

The current starter record is still a plain dictionary. Converting it to a Task instance keeps the in-memory collection consistent with the new schema.

  • In main.py, find the block that starts with tasks = [.
  • Replace that block through next_id = 2 with this code:
tasks: list[Task] = [
    Task(id=1, title="Learn REST basics", completed=False),
]
next_id = 2

What Does This Code Do?

  • tasks: list[Task] records that the collection contains Task objects.
  • Task(...) validates the starter record when the server loads.
  • next_id remains set to 2 for the next created task.
  • Save main.py.
  • Return to Swagger UI from earlier.
  • Send a request through GET /tasks.

You should still receive the starter task with id set to 1. That confirms FastAPI can serialize the typed task object into JSON.

Starter Task Missing?

  • Check that the starter record uses Task(...) with the same capitalization as the class.
  • Confirm the Task class appears above the tasks list.
  • Refresh Swagger UI after the development server finishes reloading.

Help me restore the typed starter task.

Validate task creation

The route still accepts a plain dict. Changing its body type to TaskCreate makes validation happen before create_task runs.

  • In main.py, select the complete create_task route.
  • Replace the selected route with this code:
@app.post("/tasks", status_code=201)
def create_task(task: TaskCreate):
    global next_id
    new_task = Task(id=next_id, **task.model_dump())
    tasks.append(new_task)
    next_id += 1
    return new_task

What Does This Route Do?

  • task: TaskCreate tells FastAPI to validate the request body against the input schema.
  • task.model_dump() converts the validated model into a dictionary.
  • Task(...) combines the validated fields with the next numeric ID.
  • new_task stores the complete record returned with status code 201.
  • Save main.py.
  • Refresh http://127.0.0.1:8000/docs after the server reloads.
  • Expand POST /tasks to view its generated request schema.

Before you send the first request, do you expect the empty object to reach create_task?

  • Submit {} through POST /tasks.

Swagger UI now shows a 422 validation response. The missing title prevents the route from storing the object.

  • Submit {"title":"","completed":false} through the same route.

You should receive another 422 response. The empty title violates the minimum length constraint.

  • Submit {"title":"Practice HTTP methods","completed":false} through the same route.

This request returns 201. The response includes an integer id for the new task.

  • Send another request through GET /tasks.

You should see the starter task plus the valid task you created. The two rejected bodies do not appear in the collection.

You closed the validation gap. Your API now blocks malformed task data before it reaches the in-memory list.

Invalid Tasks Still Being Accepted?

  • Confirm the function parameter is task: TaskCreate instead of task: dict.
  • Check that new_task uses task.model_dump().
  • Refresh Swagger UI after the latest server reload.

Help me diagnose why invalid task bodies still pass validation.

✔️ Awesome, I've got everything!

Great. Double-check that you saved main.py before moving on.

ⓧ I'd like to double check the full code

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=100)
    completed: bool = False


class Task(TaskCreate):
    id: int


tasks: list[Task] = [
    Task(id=1, title="Learn REST basics", completed=False),
]
next_id = 2


@app.get("/")
def root():
    return {"message": "Task Tracker API is running"}


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


@app.post("/tasks", status_code=201)
def create_task(task: TaskCreate):
    global next_id
    new_task = Task(id=next_id, **task.model_dump())
    tasks.append(new_task)
    next_id += 1
    return new_task

Your API now creates records from validated data. Next, you will make individual tasks readable, replaceable, and removable through their numeric IDs.

Complete CRUD Operations

Your FastAPI API already accepts validated JSON bodies. It can create tasks without allowing incomplete data through.

A useful REST resource also lets clients address one record. Numeric path parameters give each task its own address. HTTPException turns missing IDs into predictable 404 responses.

In this step, get ready to:
  • Add reusable lookup logic for individual tasks.
  • Retrieve or replace a task through its numeric ID.
  • Delete a task before confirming the missing record returns a clear 404 response.
Add task lookup and retrieval

Each task_id arrives through a path parameter. A shared find_task() helper centralizes the lookup. It also handles missing tasks consistently.

  • In main.py, find this import:
from fastapi import FastAPI

What Does the Current Import Provide?

The current line makes FastAPI available for creating the application. The missing-task response needs one more class from the same package.

  • Replace the current import with this updated line:
from fastapi import FastAPI, HTTPException

What Does the Updated Import Add?

HTTPException lets route logic stop a request with a chosen status code. You will use it when no task matches the requested ID.

  • In main.py, find next_id = 2.
  • Add the lookup helper directly below that line by copying this code:
def find_task(task_id: int) -> Task:
    for task in tasks:
        if task.id == task_id:
            return task
    raise HTTPException(status_code=404, detail="Task not found")

What Does This Helper Do?

  • The for loop checks each stored task for a matching ID.
  • A match returns the corresponding Task instance.
  • A missing match raises an HTTPException with status 404.
  • Save main.py.
  • Switch back to the server terminal from earlier.

The server reloads main.py without showing an import or startup problem. This proves the helper is available to your routes.

Server Reload Failing?

  • Confirm the first line contains both FastAPI and HTTPException.
  • Check that find_task() sits below next_id = 2 with the same indentation shown above.

Help me fix the lookup helper in my task API.

  • In main.py, locate the list_tasks() function.
  • Add the single-task route directly below list_tasks() by copying this code:
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
    return find_task(task_id)

How Does Retrieval Work?

  • The route places task_id inside the URL path.
  • The int annotation requires a numeric path value.
  • The route delegates lookup behavior to find_task().
  • Save main.py.
  • Return to the Swagger UI tab from earlier.
  • Refresh the page.
  • Expand GET /tasks/{task_id}.
  • Enter 1 in the task_id field.
  • Run the request from the operation panel.

You will see the starter task with id set to 1. Its title is Learn REST basics.

Task Retrieval Not Working?

  • Confirm the route path contains {task_id} exactly.
  • Check that get_task() passes its task_id value to find_task().

Help me debug my single-task route.

Replace an existing task

A PUT operation replaces the current task data. Your Pydantic model validates the replacement body before the route changes the stored record.

  • In main.py, locate the create_task() function.
  • Add the replacement route directly below create_task() by copying this code:
@app.put("/tasks/{task_id}")
def update_task(task_id: int, task: TaskCreate):
    for index, existing_task in enumerate(tasks):
        if existing_task.id == task_id:
            updated_task = Task(id=task_id, **task.model_dump())
            tasks[index] = updated_task
            return updated_task
    raise HTTPException(status_code=404, detail="Task not found")

How Does Replacement Work?

  • enumerate(tasks) provides the position of each stored task.
  • Task(...) keeps the requested ID while applying the validated replacement data.
  • tasks[index] replaces the matching record in memory.
  • The final HTTPException handles IDs that do not exist.
  • Save main.py.
  • Return to Swagger UI.
  • Refresh the page.
  • Expand PUT /tasks/{task_id}.
  • Enter 1 in the task_id field.
  • Set the request body's title to Practice HTTP methods.
  • Set completed to false.
  • Run the replacement request.

The response keeps id set to 1. The title now reads Practice HTTP methods.

Task Not Updating?

  • Confirm tasks[index] = updated_task remains inside the matching-ID block.
  • Check that Task receives the original task_id.

Help me debug my task replacement route.

Delete tasks and verify CRUD

A successful deletion returns status 204 without a response body. Reusing find_task() gives the delete route the same missing-record behavior as retrieval.

  • In main.py, locate the update_task() function.
  • Add the deletion route directly below update_task() by copying this code:
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
    task = find_task(task_id)
    tasks.remove(task)

How Does Deletion Work?

  • status_code=204 declares that a successful deletion has no response body.
  • find_task() returns the matching task or raises the shared 404 response.
  • tasks.remove(task) removes the matched object from memory.
  • Save main.py.
  • Return to Swagger UI.
  • Refresh the page.

You will now see DELETE /tasks/{task_id} alongside the create, list, retrieve, and replace operations.

✔️ Awesome, I've got everything!

Your cumulative main.py file now contains every route required for the final CRUD test.

ⓧ I'd like to double check the full code

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()


class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=100)
    completed: bool = False


class Task(TaskCreate):
    id: int


tasks: list[Task] = [
    Task(id=1, title="Learn REST basics", completed=False),
]
next_id = 2


def find_task(task_id: int) -> Task:
    for task in tasks:
        if task.id == task_id:
            return task
    raise HTTPException(status_code=404, detail="Task not found")


@app.get("/")
def root():
    return {"message": "Task Tracker API is running"}


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


@app.get("/tasks/{task_id}")
def get_task(task_id: int):
    return find_task(task_id)


@app.post("/tasks", status_code=201)
def create_task(task: TaskCreate):
    global next_id
    new_task = Task(id=next_id, **task.model_dump())
    tasks.append(new_task)
    next_id += 1
    return new_task


@app.put("/tasks/{task_id}")
def update_task(task_id: int, task: TaskCreate):
    for index, existing_task in enumerate(tasks):
        if existing_task.id == task_id:
            updated_task = Task(id=task_id, **task.model_dump())
            tasks[index] = updated_task
            return updated_task
    raise HTTPException(status_code=404, detail="Task not found")


@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
    task = find_task(task_id)
    tasks.remove(task)

What Does the Full File Contain?

This reference combines the validated models with the lookup helper. It also includes every create, list, retrieve, replace, and delete route in its final position.

The final check follows one task through its complete lifecycle. Each request proves a different part of your CRUD behavior.

  • Create another valid task through POST /tasks using the Practice HTTP methods body from earlier.
  • Record the returned numeric id here: your task ID.

The create request returns status 201. Your new record now has an addressable ID.

  • Retrieve your task ID through GET /tasks/{task_id}.

You will see the same task object returned with its numeric ID.

  • Replace your task ID through PUT /tasks/{task_id} with completed set to true.

The returned task keeps the same ID. Its completed value is now true.

Before you delete the task, what status do you expect from an operation that returns no content?

  • Delete your task ID through DELETE /tasks/{task_id}.

You will see status 204 with no response body. The task has been removed from memory.

Before you run one last retrieval, do you expect the deleted task to return successfully?

  • Request your task ID again through GET /tasks/{task_id}.

You will see status 404 with {"detail":"Task not found"} in the response body. The API now handles the full lifecycle plus the missing-record case.

Final CRUD Sequence Breaking?

  • Use the ID returned by your latest create request for every later operation.
  • Confirm the replace request contains a non-empty title.
  • Check that the final retrieval happens after the delete request returns 204.

Help me debug my full CRUD verification sequence.

Strong finish. Your API now supports the full CRUD lifecycle with validated data.

Secret mission

Filter Tasks by Completion Status

Your task list currently returns the whole collection. Add an optional completion filter so clients can request completed tasks, incomplete tasks, or everything.

Clean Up Your Resources

Clean Up Your Resources

Your FastAPI project runs entirely on your Mac with no ongoing costs. Choose whether to keep the server available, stop it for later, or delete the local project files.

Resources you used:

  • A local FastAPI development server with Swagger UI.
  • A local task-tracker-api folder containing main.py, requirements.txt, and .venv.

Keep everything running

No action needed. Choose this if you are still testing your Task Tracker API.

The development server keeps Swagger UI available at http://127.0.0.1:8000/docs. Your tasks remain in memory while the server process continues running.

Pause - I'll come back to this later

Shut down the local server to free its memory while keeping every project file for later.

  • Switch back to the project terminal from earlier.
  • Press Ctrl+C to stop the FastAPI development server.
  • Refresh Swagger UI in your browser.

The API page no longer loads. This confirms that the local server process has stopped.

Delete - I don't want to use this again

Remove all local project resources if you want to start fresh. This deletion is permanent and stays confined to the task-tracker-api folder.

  • Switch back to the project terminal from earlier.
  • Press Ctrl+C to stop the FastAPI development server.
  • Move to the folder above task-tracker-api by running this command:
cd ..

What Does This Command Do?

This moves your shell one folder up. You are now outside the project folder that you are about to remove.

  • Delete the task-tracker-api folder by running this command:
rm -rf task-tracker-api

What Does This Command Do?

This permanently removes the project folder plus everything inside it. The deletion stays scoped to task-tracker-api.

Folder Still Listed?

  • Check that your terminal is in the folder directly above task-tracker-api.
  • Ask for help with the removal command: Help me safely remove my local task-tracker-api folder from macOS Terminal.
  • Confirm the removal by listing the contents of the parent folder:
ls

What Should I See?

You should see the contents of the parent folder. The task-tracker-api entry should be absent.

Nice Work!

Nice Work!

You did it! Your local Task Tracker REST API now supports validated CRUD workflows through FastAPI's interactive documentation.

You've learned how to:

  • Design CRUD routes for an in-memory task collection. You can address individual records through numeric path parameters.
  • Use Pydantic models to validate JSON request bodies. You proved malformed tasks receive a 422 response before route logic runs.
  • Demonstrate the API through Swagger UI. You confirmed deliberate status code behavior for 201, 204, 404, and 422 responses.
  • Secret Mission: Add an optional Boolean query parameter that filters tasks by completion status without changing the route path.

Ready to quiz yourself?