Build a Task Manager API with FastAPI
Build a REST API with automatic data validation and interactive docs.
Introduction
30 Second Summary
Every app you use daily talks to a backend through an API. When you check the weather, add a reminder, or mark a task as done, your phone sends a request to a server that processes it and sends back a response.
In this project, you will build a Task Manager API from scratch using FastAPI. You will create endpoints that let users add, view, update, and delete tasks, with automatic data validation and interactive documentation built in.
What You'll Build
A fully functional REST API with full CRUD (Create, Read, Update, Delete) operations, automatic request validation powered by Pydantic, and self-generating interactive documentation.
By the end of this project, you'll have:
- A FastAPI application with 5 REST endpoints running locally on your machine.
- Pydantic models that automatically validate incoming data and return clear error messages for invalid requests.
- Auto-generated Swagger UI documentation you can use to test every endpoint directly in your browser.
- Secret Mission: Add a priority system and a search endpoint that filters tasks by keyword.
Are there any prerequisites?
You should be comfortable with basic Python concepts like variables, functions, lists, and dictionaries. No prior API or web development experience is needed.
Set Up Your Development Environment
Before you can build an API, you need the right tools installed on your machine. This step gets your development environment ready so you can focus on writing code in the next steps.
You'll verify that Python is installed, create an isolated project folder with a virtual environment, and install FastAPI with all its dependencies.
In this step, get ready to:
- Verify Python 3.10+ is installed.
- Create a project directory with a Python virtual environment.
- Install FastAPI and confirm it's ready to use.
Verify Python is installed
FastAPI requires Python 3.10 or higher. Let's check which version you have.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
You should see a version number printed in your terminal.
✔️ I see version 3.10 or higher
You're all set. Python is ready for this project. Move on to the next substep below.
ⓧ I see an older version
You need Python 3.10 or higher for FastAPI. Download the latest version from the official Python website.
- Visit python.org/downloads in your browser.
- Click the Download Python button to get the latest version.
- Run the installer and follow the prompts.
- On Windows, make sure to check the Add python.exe to PATH checkbox during installation.
- Close and reopen your terminal, then run the version check command again to confirm.
macOS default Python
Some macOS versions ship with an older Python. After installing a newer version from python.org, the python3 command should point to your new installation automatically.
ⓧ Command not found
Python isn't installed on your machine yet. Let's install it.
- Visit python.org/downloads in your browser.
- Click the Download Python button to get the latest version.
- Run the downloaded installer and follow the prompts.
- On Windows, make sure to check the Add python.exe to PATH checkbox during installation.
- Close and reopen your terminal, then run the version check command again to confirm it works.
Create your project directory and virtual environment
A virtual environment gives your project its own isolated set of packages. This prevents conflicts with other Python projects on your machine.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir task-manager-api
cd task-manager-api
- Create a virtual environment inside your project folder by running this command:
macOS
python3 -m venv .venv
Windows
python -m venv .venv
- Activate the virtual environment by running:
macOS
source .venv/bin/activate
Windows
.venv\Scripts\activate
You should see (.venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated Python workspace for your project. Packages you install here stay separate from your system Python and other projects.
The .venv folder holds this isolated environment. You activate it each time you work on your project so that pip installs packages in the right place.
Don't see (.venv) in your prompt?
Make sure you ran the activate command for your operating system. On macOS it's source .venv/bin/activate and on Windows it's .venv\Scripts\activate.
If you see a permissions error on Windows, try running Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned in PowerShell first, then retry the activate command.
Help me activate my Python virtual environment.
Install FastAPI
With your virtual environment active, you can now install FastAPI and all the tools it needs to run a development server.
- Install FastAPI with its standard dependencies by running:
pip install "fastapi[standard]"
What does this command do?
The [standard] part tells pip to install FastAPI plus all recommended extras. This includes a development server with auto-reload, so your API restarts automatically every time you save a change.
You'll see a stream of download and installation messages. Wait until you see a success message at the end.
- Verify the installation by running:
fastapi --help
You should see a help message listing available commands like dev and run. This confirms FastAPI is installed and ready to use.
Seeing 'command not found' for fastapi?
Make sure your virtual environment is still active. You should see (.venv) at the start of your terminal prompt.
If it's not active, re-run the activate command from the previous substep. Then try fastapi --help again.
Help me fix 'fastapi command not found'.
Your environment is ready. Next up, you'll create your first FastAPI endpoint and see it running in the browser.
Create Your First FastAPI Endpoint
Your development environment is set up with Python, a virtual environment, and FastAPI installed. Now it's time to write actual code and see your API come to life.
Every API needs at least one endpoint for clients to send requests to. In this step, you'll create a minimal FastAPI application, start a live development server, and explore the interactive documentation that FastAPI generates for you automatically.
In this step, get ready to:
- Create main.py with a root GET endpoint.
- Run the FastAPI development server.
- Explore the auto-generated Swagger UI documentation.
Create your FastAPI application file
A REST API endpoint is a specific URL that responds to a particular type of request. You'll define your first one now.
- In your editor, create a new file called main.py in your project folder.
- Add the following code to main.py:
# Import the FastAPI class
from fastapi import FastAPI
# Create the application instance
app = FastAPI()
# Define a GET endpoint at the root URL
@app.get("/")
def read_root():
return {"message": "Welcome to the Task Manager API"}
What does this code do?
- from fastapi import FastAPI brings in the main class you need to build your API.
- app = FastAPI() creates your application instance. This is the object that the server will run.
- @app.get("/") is a decorator that registers the function below it as a GET endpoint at the root URL (/).
- The function returns a Python dictionary. FastAPI automatically converts it to a JSON response.
- Save main.py.
- You won't see a change yet. The next substep starts the server so you can test this endpoint.
✔️ 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 read_root():
return {"message": "Welcome to the Task Manager API"}
Run the development server
FastAPI includes a built-in development server that auto-reloads whenever you save a change to your code. It automatically finds the app object in main.py.
- Start the development server by running this command in your terminal:
fastapi dev
You should see output including the line Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit).
- Open your browser and navigate to http://127.0.0.1:8000.
- You should see a JSON response: {"message":"Welcome to the Task Manager API"}.
What is 127.0.0.1:8000?
127.0.0.1 is the IP address for your own machine (localhost). Port 8000 is where FastAPI's dev server listens by default. Together they give you a local URL to test your API without deploying anything.
Not seeing the JSON response?
- Make sure your virtual environment is still activated. If you see a command-not-found error for fastapi, reactivate your venv.
- Check that main.py is saved in the same folder where you ran the command. The CLI auto-detects main.py in the current directory.
- If port 8000 is already in use, stop the other process or close the terminal tab that has it running.
My server won't start after checking all of the above.
Explore the auto-generated documentation
One of FastAPI's standout features is that it generates interactive API documentation from your code automatically. You don't write or maintain it yourself.
- In your browser, navigate to http://127.0.0.1:8000/docs.
You should see the Swagger UI page listing your GET / endpoint. You can expand it, click Try it out, then Execute to send a test request directly from your browser.
Why is this useful?
Swagger UI lets you test every endpoint without writing any client code or using a separate tool. As you add more endpoints in the next steps, they appear here automatically.
FastAPI generates this from your Python type hints and function signatures. The more detail you add to your code, the richer the documentation becomes.
Your first endpoint is live and self-documenting. Next up, you'll define structured data models so FastAPI can automatically validate every request that comes in.
Define Your Task Data Model with Pydantic
Your API is running and automatically documenting itself. Now it's time to give it something meaningful to work with: structured data.
Every API needs to know what shape of data it accepts and returns. FastAPI uses Pydantic models to define that shape, which gives you automatic validation for free.
In this step, get ready to:
- Define Pydantic models for task input and output.
- Set up in-memory storage for your tasks.
- Create a POST endpoint to add new tasks.
Create Pydantic models for your tasks
Pydantic models are Python classes that define what fields your data should have and what types those fields expect. When someone sends data to your API, FastAPI uses these models to automatically validate the request and reject anything that doesn't match.
- In main.py, find the import line at the top:
from fastapi import FastAPI
- Replace it with the following updated imports and Pydantic model definitions, placing the models between app = FastAPI() and the read_root function:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
# Model for creating a new task (input from the client)
class TaskCreate(BaseModel):
title: str
description: str | None = None
completed: bool = False
# Model for task responses (includes the generated ID)
class Task(BaseModel):
id: int
title: str
description: str | None = None
completed: bool = False
What does this code do?
You're defining two models that describe the shape of a task at different stages.
- TaskCreate is what clients send when creating a task. It has a required title, an optional description, and a completed flag that defaults to False.
- Task is what the API returns. It includes everything from TaskCreate plus an id field that the server generates.
- The str | None = None syntax means a field is optional. If the client doesn't send it, it defaults to None.
- Save main.py.
You won't see a change in the browser yet. The models don't appear in the Swagger UI until an endpoint uses them.
Seeing a syntax error on the server?
- Check that both import lines are at the very top of the file with no indentation.
- Make sure you're using str | None (pipe syntax), not Optional[str]. The pipe syntax requires Python 3.10+.
- Confirm that each class line ends with (BaseModel): including the colon.
Why is my Pydantic model causing a syntax error?
Add in-memory storage and a POST endpoint
Your models define the shape of a task, but you also need somewhere to store them. For this project, a simple Python list works perfectly as in-memory storage.
- In main.py, add the following code between your Task model and the read_root function:
# In-memory storage for tasks
tasks: list[Task] = []
task_id_counter: int = 1
@app.get("/")
def read_root():
return {"message": "Welcome to the Task Manager API"}
# Create a new task and add it to the list
@app.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(task_data: TaskCreate):
global task_id_counter
new_task = Task(id=task_id_counter, **task_data.model_dump())
tasks.append(new_task)
task_id_counter += 1
return new_task
What does this code do?
This adds both the storage layer and your first data-writing endpoint.
- tasks: list[Task] = [] creates an empty list to hold your tasks in memory. Data resets every time the server restarts.
- task_id_counter tracks the next available ID so each task gets a unique number.
- @app.post("/tasks") registers a POST endpoint. The response_model=Task tells FastAPI what shape the response will be, and status_code=status.HTTP_201_CREATED returns a 201 instead of the default 200.
- task_data.model_dump() converts the Pydantic model into a dictionary, which gets unpacked into the Task constructor along with the generated id.
- Save main.py.
- Switch back to your browser and refresh http://127.0.0.1:8000/docs. You should see a new POST /tasks endpoint listed in the Swagger UI.
- Click the POST /tasks row to expand it, then click Try it out.
- Replace the example JSON body with this and click Execute:
{
"title": "Learn FastAPI",
"description": "Build a task manager API",
"completed": false
}
You should see a 201 response with your task data, including an id of 1 that the server generated.
Not seeing the POST endpoint in Swagger UI?
- Make sure you saved main.py. The fastapi dev server auto-reloads on save, but only if the file is actually written to disk.
- Check your terminal for error messages. A missing comma or colon in the decorator line will prevent the server from reloading.
- Confirm that @app.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED) is at the same indentation level as @app.get("/").
Why isn't my new POST endpoint showing up in the docs?
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and your server is running without errors.
ⓧ 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, status
from pydantic import BaseModel
app = FastAPI()
class TaskCreate(BaseModel):
title: str
description: str | None = None
completed: bool = False
class Task(BaseModel):
id: int
title: str
description: str | None = None
completed: bool = False
tasks: list[Task] = []
task_id_counter: int = 1
@app.get("/")
def read_root():
return {"message": "Welcome to the Task Manager API"}
@app.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(task_data: TaskCreate):
global task_id_counter
new_task = Task(id=task_id_counter, **task_data.model_dump())
tasks.append(new_task)
task_id_counter += 1
return new_task
Your API can now create and store tasks with automatic validation. Next up, you'll add endpoints to retrieve tasks with filtering built in.
Build Read Endpoints with Path and Query Parameters
Your API can create tasks now. But what good is stored data if you can't retrieve it?
In this step, you'll build two GET endpoints that let users read tasks back from the API. One returns the full list (with optional filtering), and the other fetches a single task by its ID.
In this step, get ready to:
- Create a GET endpoint that returns all tasks with optional filtering by completion status.
- Create a GET endpoint that retrieves a single task by its ID.
Create the GET all tasks endpoint with query parameter filtering
Right now your API has no way to list existing tasks. You need a GET endpoint at /tasks that returns every task in the list.
You'll also add an optional query parameter so users can filter tasks by their completion status.
- In main.py, add the following code below your create_task() function:
@app.get("/tasks", response_model=list[Task])
def get_tasks(completed: bool | None = None):
# If the completed filter is provided, return only matching tasks
if completed is not None:
return [task for task in tasks if task.completed == completed]
# Otherwise, return all tasks
return tasks
What does this code do?
This endpoint handles two scenarios depending on whether the user includes a completed query parameter in their request.
- completed: bool | None = None declares an optional query parameter. FastAPI automatically treats function parameters that are not in the URL path as query parameters.
- If a user visits /tasks?completed=true, only completed tasks are returned. If they visit /tasks with no parameter, all tasks come back.
- response_model=list[Task] tells FastAPI that this endpoint returns a list of Task objects, which powers the auto-generated documentation.
- Save main.py.
- Switch back to the terminal where fastapi dev is running. If you stopped it earlier, start it again by running:
fastapi dev
- Open http://127.0.0.1:8000/docs in your browser. You should see a new GET /tasks endpoint listed in the Swagger UI.
Test with real data
Before testing GET, use the POST /tasks endpoint in Swagger UI to create a couple of tasks first. Then try GET /tasks to see them returned.
Don't see the new endpoint?
Make sure you saved main.py. The dev server auto-reloads on save, but unsaved changes won't appear.
Check that the new function is placed after create_task() and is not accidentally indented inside another function.
help me debug why my GET /tasks endpoint isn't showing up in Swagger UI
Create the GET single task endpoint with a path parameter
Listing all tasks is useful, but users also need to retrieve one specific task by its ID. This requires a path parameter that captures the task ID directly from the URL.
- In main.py, add the following code below your get_tasks() function:
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
# Search through tasks for a matching ID
for task in tasks:
if task.id == task_id:
return task
# If no task found, return a 404 error
raise HTTPException(status_code=404, detail="Task not found")
What does this code do?
This endpoint uses a path parameter to find a specific task.
- /tasks/{task_id} tells FastAPI that the URL contains a variable segment. A request to /tasks/3 sets task_id to 3.
- task_id: int in the function signature makes FastAPI automatically validate that the value is an integer. A request to /tasks/abc returns a validation error automatically.
- raise HTTPException(status_code=404, detail="Task not found") sends back a JSON error response with a 404 status code if no task matches the given ID.
- Save main.py.
- Return to http://127.0.0.1:8000/docs in your browser. You should see a new GET /tasks/{task_id} endpoint.
- Test it by expanding the endpoint, clicking Try it out, entering 1 as the task_id, and clicking Execute. You should see your first task returned as JSON.
- Try entering a task_id that doesn't exist (like 999). You should see a 404 response with the message Task not found.
Getting a 422 validation error?
If you see a 422 response, FastAPI is telling you the input failed validation. Make sure you entered a whole number (like 1) in the task_id field, not text or a decimal.
If you get a 404 even for task_id 1, remember that the in-memory list resets every time the server restarts. Create a task with POST first, then try GET.
help me debug why my GET /tasks/{task_id} endpoint returns unexpected errors
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and your dev server is still running.
ⓧ I'd like to double check the full code
Here's the complete main.py file up to this point:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
class TaskCreate(BaseModel):
title: str
description: str | None = None
completed: bool = False
class Task(BaseModel):
id: int
title: str
description: str | None = None
completed: bool = False
tasks: list[Task] = []
task_id_counter: int = 1
@app.get("/")
def read_root():
return {"message": "Welcome to the Task Manager API"}
@app.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(task_data: TaskCreate):
global task_id_counter
new_task = Task(id=task_id_counter, **task_data.model_dump())
tasks.append(new_task)
task_id_counter += 1
return new_task
@app.get("/tasks", response_model=list[Task])
def get_tasks(completed: bool | None = None):
if completed is not None:
return [task for task in tasks if task.completed == completed]
return tasks
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
for task in tasks:
if task.id == task_id:
return task
raise HTTPException(status_code=404, detail="Task not found")
Your API now handles both creating and reading tasks. Next up, you'll add the ability to update and delete tasks to complete the full CRUD cycle.
Add Update and Delete Endpoints
Your API can create and read tasks. The next piece is letting users modify existing tasks and remove ones they no longer need.
In this step, you'll complete the full CRUD lifecycle by adding a PUT endpoint for updating task fields and a DELETE endpoint for removing tasks. Both follow the same pattern: search by ID, act on the task, or raise a 404 error if it doesn't exist.
In this step, get ready to:
- Define a TaskUpdate model with optional fields for partial updates.
- Create PUT and DELETE endpoints for modifying and removing tasks.
- Test all five CRUD endpoints through the Swagger UI.
Create the TaskUpdate model
Updates are different from creation. When a user updates a task, they might only want to change the title without touching the description or status. You need a model where every field is optional.
- In main.py, find the TaskCreate class. Add the following TaskUpdate class directly below it (before the Task class):
class TaskUpdate(BaseModel):
# All fields optional - only provided fields get updated
title: str | None = None
description: str | None = None
completed: bool | None = None
What does this code do?
Every field defaults to None. When a user sends an update request, they only include the fields they want to change.
Later, you'll use model_dump(exclude_unset=True) to extract only the fields the user actually sent. This prevents accidentally overwriting existing values with None.
- Save main.py.
- Your development server auto-reloads. You won't see a visible change yet because the model isn't used by an endpoint. The next chunk connects it.
Create the PUT endpoint for updating tasks
The PUT endpoint finds a task by ID, applies only the fields the user sent, and returns the updated task.
- In main.py, find the get_task function at the bottom of your file. Add the following PUT endpoint below it:
@app.put("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, task_data: TaskUpdate):
for index, task in enumerate(tasks):
if task.id == task_id:
# Extract only the fields the user actually sent
update_data = task_data.model_dump(exclude_unset=True)
# Create a new task copy with those fields updated
updated_task = task.model_copy(update=update_data)
tasks[index] = updated_task
return updated_task
raise HTTPException(status_code=404, detail="Task not found")
What does this code do?
- enumerate(tasks) loops through the list while tracking each task's position (index). You need the index to replace the old task with the updated one.
- model_dump(exclude_unset=True) converts the request body to a dictionary but only includes fields the user explicitly sent. If they only sent a new title, only title appears in the dictionary.
- model_copy(update=update_data) creates a copy of the existing task with the changed fields applied. The original task object stays untouched until you assign the copy back into the list.
- If no task matches the ID, the function raises a 404 error, just like the GET single-task endpoint.
- Save main.py.
- Switch to your browser and refresh http://127.0.0.1:8000/docs. You should see a new green PUT /tasks/{task_id} row in the endpoint list.
Don't see the PUT endpoint?
- Make sure you saved main.py. The dev server only reloads after a file save.
- Check your terminal for error messages. A syntax error (missing colon, wrong indentation) prevents the server from reloading.
- Confirm the new function is outside the get_task function, not accidentally indented inside it.
Why isn't my PUT endpoint showing up in the Swagger UI?
Create the DELETE endpoint for removing tasks
The DELETE endpoint removes a task from the list entirely and confirms what was deleted.
- In main.py, add the following DELETE endpoint below the update_task function:
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
for index, task in enumerate(tasks):
if task.id == task_id:
# Remove the task from the list
tasks.pop(index)
return {"message": f"Task {task_id} deleted successfully"}
raise HTTPException(status_code=404, detail="Task not found")
What does this code do?
- This endpoint doesn't have a response_model because it returns a simple confirmation dictionary, not a Task object.
- tasks.pop(index) removes the task at that position from the list permanently.
- The f-string in the return message includes the deleted task's ID so the user gets clear confirmation of which task was removed.
- Save main.py.
- Refresh http://127.0.0.1:8000/docs. You should see a red DELETE /tasks/{task_id} row now listed alongside your other endpoints.
Don't see the DELETE endpoint?
- Check that the @app.delete decorator is at the top level (no extra indentation under update_task).
- Look in your terminal for any traceback or syntax error messages that stopped the auto-reload.
Why isn't my DELETE endpoint showing up in the docs?
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py before moving on to testing.
ⓧ I'd like to double check the full code
Here's the complete main.py file with all five endpoints:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
class TaskCreate(BaseModel):
title: str
description: str | None = None
completed: bool = False
class TaskUpdate(BaseModel):
title: str | None = None
description: str | None = None
completed: bool | None = None
class Task(BaseModel):
id: int
title: str
description: str | None = None
completed: bool = False
tasks: list[Task] = []
task_id_counter: int = 1
@app.get("/")
def read_root():
return {"message": "Welcome to the Task Manager API"}
@app.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(task_data: TaskCreate):
global task_id_counter
new_task = Task(id=task_id_counter, **task_data.model_dump())
tasks.append(new_task)
task_id_counter += 1
return new_task
@app.get("/tasks", response_model=list[Task])
def get_tasks(completed: bool | None = None):
if completed is not None:
return [task for task in tasks if task.completed == completed]
return tasks
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
for task in tasks:
if task.id == task_id:
return task
raise HTTPException(status_code=404, detail="Task not found")
@app.put("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, task_data: TaskUpdate):
for index, task in enumerate(tasks):
if task.id == task_id:
update_data = task_data.model_dump(exclude_unset=True)
updated_task = task.model_copy(update=update_data)
tasks[index] = updated_task
return updated_task
raise HTTPException(status_code=404, detail="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": f"Task {task_id} deleted successfully"}
raise HTTPException(status_code=404, detail="Task not found")
Test all CRUD operations through the Swagger UI
Your API now has all five endpoints. Time to run through a full create-read-update-delete cycle using the interactive documentation.
- In your browser, go to http://127.0.0.1:8000/docs.
- Expand the POST /tasks endpoint and click **Try it out**.
- Replace the example JSON body with this:
{
"title": "Buy groceries",
"description": "Milk, eggs, bread",
"completed": false
}
- Click **Execute**. You should see a 201 response with your new task, including "id": 1.
- Now expand the PUT /tasks/{task_id} endpoint and click **Try it out**.
- Enter 1 in the task_id field.
- Replace the request body with:
{
"completed": true
}
- Click **Execute**. You should see the task returned with "completed": true while the title and description remain unchanged.
- Now expand the DELETE /tasks/{task_id} endpoint and click **Try it out**.
- Enter 1 in the task_id field and click **Execute**.
- You should see "message": "Task 1 deleted successfully" in the response.
What about validation errors?
Try entering 999 as the task_id in the PUT or DELETE endpoint. You'll get a 404 response with "detail": "Task not found". FastAPI handles error responses automatically from your HTTPException.
Try sending an invalid body to POST (like {"completed": "yes"} without a title). Pydantic returns a 422 error explaining exactly which field failed validation and why.
- Finally, expand the GET /tasks endpoint, click **Try it out**, and click **Execute**. You should see an empty list [] confirming the task was deleted.
Seeing unexpected results?
- If GET /tasks returns tasks you don't expect, remember that data resets every time the server reloads. Saving main.py triggers a reload and clears all in-memory data.
- If PUT returns "completed": null for fields you didn't send, double check that your TaskUpdate model uses exclude_unset=True in model_dump(). Without it, all None defaults get applied.
Help me debug my CRUD test results in Swagger UI.
Secret mission
Add Priority and Search
Your API handles basic CRUD, but real task managers need priority levels and search. Can you extend your models with an enum field and add a keyword search endpoint that filters tasks by title or description?
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 available, pause them to come back later, or delete them entirely.
Resources you used:
- FastAPI development server (running process in your terminal).
- Project folder on your Desktop (contains main.py, your virtual environment, and all installed packages).
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend your API with a database or authentication.
- Your FastAPI development server will continue running until you close the terminal or press Ctrl+C.
- Your project folder stays on your Desktop and is ready to use anytime.
- Python and pip remain installed on your system.
Pause - I'll come back to this later
Stop the running server to free up your terminal. Your project files stay on disk for next time.
- Switch to the terminal where your FastAPI server is running.
- Press Ctrl+C to stop the development server.
- When you come back, navigate to your project folder, activate your virtual environment, and run fastapi dev again.
Delete - I don't want to use this again
Remove all project files and free up disk space. This deletes your code, virtual environment, and all installed packages.
- Switch to the terminal where your FastAPI server is running and press Ctrl+C to stop it.
- Navigate to your Desktop and delete the project folder by running these commands:
macOS
cd ~/Desktop
rm -rf [[PROJECT_FOLDER="your-project-folder-name"]]
Windows
cd ~\Desktop
rmdir /s /q [[PROJECT_FOLDER="your-project-folder-name"]]
- Confirm the folder is gone by running:
ls
- You should no longer see your project folder listed in the output.
Nice Work!
Nice Work!
You just built a real REST API from scratch. Your Task Manager API handles full CRUD operations, validates incoming data automatically, and documents itself with interactive Swagger UI.
You've learned how to:
- Build a fully functional CRUD API with FastAPI, including path parameters, query parameters, and request bodies.
- Use Pydantic models for automatic data validation and type-safe response serialization.
- Handle errors with proper HTTP status codes and explore auto-generated interactive API documentation with Swagger UI.
- Added priority levels with enum validation and a keyword search endpoint to your API.
Ready to quiz yourself?