Build a Task Manager REST API
Build a CRUD REST API with FastAPI and explore auto-generated API docs.
Introduction
30 Second Summary
Every time you open an app on your phone and it loads fresh data, your device is talking to a server through an API. That conversation follows specific rules about how to ask for data, send new data, and confirm changes.
In this project, you will build a Task Manager REST API using Python and FastAPI. You'll create the server side of that conversation, handling requests to create, read, update, and delete tasks.
What You'll Build
A Task Manager REST API server that handles all four CRUD operations (Create, Read, Update, Delete) and validates incoming data automatically using Pydantic models.
By the end of this project, you'll have:
- A running FastAPI development server with hot-reload that picks up code changes instantly.
- Pydantic data models that automatically validate every incoming request before your code processes it.
- A full set of REST API endpoints supporting create, read, update, and delete operations, tested through interactive browser-based documentation.
- Secret Mission: Add query parameter filtering so users can retrieve only completed or incomplete tasks.
Are there any prerequisites?
You should be comfortable with basic Python concepts like variables, functions, dictionaries, and lists. No web development experience is needed.
Set Up Your Project
Every API project starts with a proper foundation. You need a dedicated folder, an isolated Python environment for your dependencies, and a framework to handle incoming HTTP requests.
In this step, you'll create your project folder, install FastAPI, and write your first working endpoint. By the end, you'll have a running server you can visit in your browser.
In this step, get ready to:
- Create a project folder and Python virtual environment.
- Install FastAPI and verify the installation.
- Write and run a minimal API endpoint.
Create a project folder and virtual environment
A virtual environment is an isolated space where Python installs packages just for one project. This keeps your system clean and avoids version conflicts between projects.
- In VS Code, open the integrated terminal by pressing Ctrl+` (macOS and Windows).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder by running this command:
mkdir task-manager
- Open the task-manager folder in VS Code. Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the task-manager folder, and click Open.
VS Code reloads with task-manager as your workspace. The Explorer sidebar on the left now shows your project folder.
- Open a new terminal in the workspace by pressing Ctrl+`.
- Create a virtual environment by running this command:
python3 -m venv .venv
- Confirm the virtual environment was created by running:
ls -a
You should see .venv listed in the output.
- Activate the virtual environment by running:
source .venv/bin/activate
Your terminal prompt now starts with (.venv). This confirms all packages you install will stay inside this project.
Don't see (.venv) in your prompt?
Make sure you typed the command exactly as shown, including the leading source keyword. If you see a "No such file or directory" error, the .venv folder was not created successfully. Re-run python3 -m venv .venv and check for errors.
Why isn't my virtual environment activating?
Install FastAPI
FastAPI is a Python framework for building REST APIs. Installing it with the standard extras gives you a development server with auto-reload built in.
- Install FastAPI and its standard dependencies by running:
pip install "fastapi[standard]"
- Verify the installation succeeded by running:
python -c "import fastapi; print(fastapi.__version__)"
You should see a version number printed in your terminal. This confirms FastAPI is installed and importable.
Seeing an error during installation?
Make sure your virtual environment is active (your prompt shows (.venv)). If you see a permissions error, do not use sudo. Instead, deactivate and recreate the virtual environment.
Help me fix my FastAPI installation error.
Create the main application file
With FastAPI installed, you can now write your first endpoint. An endpoint is a specific URL that your server listens on and responds to.
- Create the main application file by running:
touch main.py
- Confirm the file exists by running:
ls
You should see main.py listed alongside the .venv folder.
- Click on main.py in the Explorer sidebar to open it in the editor.
- Add the following code to main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Task Manager API"}
What does this code do?
- from fastapi import FastAPI pulls in the FastAPI class from the package you just installed.
- app = FastAPI() creates your application instance. This object is what the server uses to route incoming requests.
- @app.get("/") is a decorator that tells FastAPI to call the function below whenever someone visits the root URL.
- The root() function returns a Python dictionary, which FastAPI automatically converts to a JSON response.
- Save main.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Make sure you've saved your file before moving on to the next step.
ⓧ I'd like to double check the full code
Here's the complete main.py file at this point:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Task Manager API"}
- Start the development server by running this command in your terminal:
fastapi dev
What does this command do?
The fastapi dev command automatically detects the FastAPI app in main.py and starts a local server using Uvicorn. It also enables auto-reload, so any changes you save to your code are picked up instantly without restarting.
Your terminal is now occupied by the running server. You will not be able to type more commands in this terminal while the server runs.
- Open your browser and navigate to http://127.0.0.1:8000.
You should see a JSON response: {"message":"Task Manager API"}. Your server is alive and responding to requests.
Seeing "command not found" or a connection error?
If fastapi dev is not found, your virtual environment may not be active. Press Ctrl+C to stop any running process, then run source .venv/bin/activate and try again.
If the browser shows "connection refused," make sure the terminal output says the server is running on http://127.0.0.1:8000 before you try to load the page.
Help me get my FastAPI dev server running.
Your development environment is fully set up and your first endpoint is live. Next up, you'll define the data model for your tasks so the API knows what a task looks like.
Define Your Task Data Model
Your FastAPI server is up and running. Now it needs to understand what a "task" actually looks like.
In this step, you will define data models that describe the shape of your tasks. These models act as blueprints that FastAPI uses to validate incoming data automatically. If someone sends a task without a title, your API will reject it before your code even runs.
In this step, get ready to:
- Define Pydantic models that describe tasks and task inputs.
- Set up in-memory storage with sample data so you have tasks to work with immediately.
Create Pydantic models for tasks
Pydantic is a data validation library that comes bundled with FastAPI. You define a class with typed fields, and Pydantic handles all the checking for you.
- In main.py, find the line that says from fastapi import FastAPI and add this import directly below it:
from pydantic import BaseModel
- Next, add these three data model classes below app = FastAPI(). Paste the following code between the app = FastAPI() line and the @app.get("/") decorator:
# --- Data Models ---
class TaskCreate(BaseModel):
# Incoming data when creating a task (no id or status needed)
title: str
description: str | None = None
class TaskUpdate(BaseModel):
# Incoming data when updating a task (all fields optional)
title: str | None = None
description: str | None = None
completed: bool | None = None
class Task(BaseModel):
# The complete task with all fields including id and status
id: int
title: str
description: str | None = None
completed: bool = False
What do these three models do?
- TaskCreate defines what a client sends when creating a new task. It only needs a title (required) and an optional description. The server assigns the ID and completion status.
- TaskUpdate defines what a client sends when updating a task. Every field is optional so you can update just one field at a time without sending everything.
- Task is the full task as it lives in your system. It includes the id (server-generated) and completed status (defaults to False).
The str | None = None syntax means a field is optional and defaults to None if not provided.
You won't see a visible change yet. The next chunk adds sample data for these models to hold.
Set up in-memory storage
Your models define the shape of a task, but you need somewhere to actually store them. A simple Python list works perfectly for this project. It keeps the focus on REST API concepts without introducing database complexity.
- Add the in-memory storage below your Task class. Paste this code between the Task model and the @app.get("/") decorator:
# --- In-Memory Storage ---
tasks: list[Task] = [
Task(id=1, title="Learn FastAPI", description="Build a REST API project", completed=False),
Task(id=2, title="Buy groceries", description=None, completed=True),
]
next_id: int = 3
# --- Endpoints ---
What does this code do?
- tasks is a list that holds Task objects. Two sample tasks are pre-loaded so you have data to work with immediately when you build your endpoints.
- next_id is a counter that tracks what ID to assign to the next task. It starts at 3 because the two samples use IDs 1 and 2.
- The # --- Endpoints --- comment goes above your existing route to keep the file organized into clear sections.
- Save main.py.
- Check your terminal. You should see a message indicating the server detected changes and reloaded. The key line to look for is Application startup complete.
- Refresh http://127.0.0.1:8000 in your browser. You should still see {"message": "Task Manager API"}.
Server crashed or showing an error?
- Check your terminal for a red traceback. The most common cause is a typo in one of the model field types. Make sure you typed str | None = None with the pipe character (|), not the word "or".
- Confirm from pydantic import BaseModel is on its own line directly below from fastapi import FastAPI. A missing import causes a NameError.
- Make sure the # --- Endpoints --- comment and the @app.get("/") decorator are still in your file. If you accidentally deleted them, re-add them below your storage code.
Still stuck? Help me debug my FastAPI data models. The server is crashing after I added Pydantic models and in-memory storage.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py and your server is still responding.
ⓧ I'd like to double check the full code
Here is the complete main.py at this point:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# --- Data Models ---
class TaskCreate(BaseModel):
title: str
description: str | None = None
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
# --- In-Memory Storage ---
tasks: list[Task] = [
Task(id=1, title="Learn FastAPI", description="Build a REST API project", completed=False),
Task(id=2, title="Buy groceries", description=None, completed=True),
]
next_id: int = 3
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Task Manager API"}
Your data models are locked in. Next up, you will build the GET endpoints that actually read from this storage and return tasks to anyone who asks.
Build the Read Endpoints
Your API server is running and your data models are defined, but the only endpoint returns a generic welcome message. The whole point of a REST API is to let clients request and receive specific data.
In this step, you'll build the "Read" part of CRUD by creating two GET endpoints. One returns all tasks, and the other returns a single task by its ID.
In this step, get ready to:
- Build a GET endpoint that returns all tasks.
- Build a GET endpoint that returns a single task by ID.
- Explore FastAPI's interactive API documentation to test your endpoints.
Create the "list all tasks" endpoint
The first endpoint you'll build returns every task in your list. This is the most common type of GET endpoint in any API.
- In main.py, scroll down to the bottom of the file (below your root endpoint).
- Add the list all tasks endpoint by copying this code below the root() function:
@app.get("/tasks")
def list_tasks() -> list[Task]:
return tasks
What does this code do?
The @app.get("/tasks") decorator registers this function to handle GET requests at the /tasks URL path.
The -> list[Task] return type hint tells FastAPI the response shape. FastAPI uses this to validate the response and generate accurate documentation automatically.
- Save main.py.
- Open http://127.0.0.1:8000/tasks in your browser. You should see a JSON array containing your two sample tasks.
Seeing an empty page or error?
- Make sure you saved main.py. The dev server auto-reloads on save, but unsaved changes won't appear.
- Check that you pasted the code below the root() function, not inside it. The decorator should be at the same indentation level as @app.get("/").
Help me debug why my /tasks endpoint isn't returning data.
Create the "get single task" endpoint
Returning all tasks is useful, but clients often need to fetch a specific task by its ID. This requires a path parameter that captures a dynamic value from the URL.
- In main.py, find the import line at the very top of the file:
from fastapi import FastAPI
- Add HTTPException to the import so the line looks like this:
from fastapi import FastAPI, HTTPException
What is HTTPException?
HTTPException lets you return proper HTTP error responses. When a client requests a task ID that doesn't exist, you'll raise this exception to send back a 404 "Not Found" status code with a helpful error message.
- Now scroll to the bottom of main.py and add the get single task endpoint below your list_tasks() function:
@app.get("/tasks/{task_id}")
def get_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 code do?
The {task_id} in the path tells FastAPI this is a dynamic segment. Whatever value appears in the URL gets passed to the function as the task_id parameter.
Because task_id is typed as int, FastAPI automatically validates and converts the URL value. If someone visits /tasks/abc, FastAPI rejects it with a validation error before your code even runs.
- Save main.py.
- Open http://127.0.0.1:8000/tasks/1 in your browser. You should see the JSON for your "Learn FastAPI" task.
- Try visiting http://127.0.0.1:8000/tasks/99 to confirm you get a 404 error response with the message Task not found.
Getting a different error?
- If you see Internal Server Error, check your terminal for a traceback. A common cause is a typo in HTTPException or forgetting to add it to the import line.
- Make sure your import line reads from fastapi import FastAPI, HTTPException with a comma separating the two names.
Help me fix my get single task endpoint.
✔️ Awesome, I've got everything!
Great. Double check you've saved main.py and both endpoints are working in the browser 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, HTTPException
from pydantic import BaseModel
app = FastAPI()
# --- Data Models ---
class TaskCreate(BaseModel):
title: str
description: str | None = None
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
# --- In-Memory Storage ---
tasks: list[Task] = [
Task(id=1, title="Learn FastAPI", description="Build a REST API project", completed=False),
Task(id=2, title="Buy groceries", description=None, completed=True),
]
next_id: int = 3
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Task Manager API"}
@app.get("/tasks")
def list_tasks() -> list[Task]:
return tasks
@app.get("/tasks/{task_id}")
def get_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")
Explore the interactive docs
FastAPI automatically generates an interactive API playground based on your endpoint definitions. You can test every endpoint without writing any client code.
- Open http://127.0.0.1:8000/docs in your browser.
- You should see a page titled Swagger UI listing all three of your endpoints (/, /tasks, and /tasks/{task_id}).
- Click on the GET /tasks row to expand it.
- Click the Try it out button on the right side.
- Click Execute. You should see the full JSON response appear below with your two sample tasks.
- Now expand GET /tasks/{task_id}, click Try it out, enter 1 in the task_id field, and click Execute.
Why is this useful?
Swagger UI is generated automatically from your code. Every time you add a new endpoint or change a model, the docs update on their own. This means you always have up-to-date, testable documentation without maintaining a separate tool.
Your read endpoints are working and you've explored the interactive docs. Next up, you'll build the POST endpoint to create new tasks through your API.
Build the Create Endpoint
Your API can read tasks, but it can't create new ones yet. Right now, the only tasks available are the two samples you hardcoded into the list.
In this step, you'll build a POST endpoint that accepts task data from a client and adds a brand new task to your in-memory storage. This is the "C" in CRUD (Create, Read, Update, Delete).
In this step, get ready to:
- Define a POST endpoint that creates tasks with automatic ID assignment.
- Test creating tasks using Swagger UI and verify they appear in the task list.
Define the POST endpoint
A POST request tells the server to create something new. Your endpoint will receive task data from the client, assign it a unique ID, and store it in the tasks list.
- In main.py, scroll to the bottom of the file (after the get_task function) and add the following code:
@app.post("/tasks", status_code=201)
def create_task(task_data: TaskCreate) -> Task:
# Access the module-level ID counter
global next_id
# Build a full Task from the incoming data and the next available ID
new_task = Task(id=next_id, title=task_data.title, description=task_data.description)
# Store the task and increment the counter for the next one
tasks.append(new_task)
next_id += 1
return new_task
What does this code do?
@app.post("/tasks", status_code=201) registers this function as a POST handler at /tasks. The status_code=201 tells the client that a resource was successfully created (instead of the default 200 OK).
task_data: TaskCreate means FastAPI will automatically parse the incoming JSON body into a TaskCreate model. If the client sends invalid data (like missing the title), FastAPI returns a 422 error automatically.
global next_id lets the function modify the module-level next_id counter so each new task gets a unique ID that keeps incrementing.
- Save main.py.
- Your dev server auto-reloads on save. Check the terminal to confirm you see a reload message without any errors.
Seeing a syntax or indentation error?
Make sure the new function starts at the same indentation level as get_task above it (no extra indentation). Also check that there is a blank line between the end of get_task and the @app.post decorator.
Help me fix a syntax error in my POST endpoint
Test creating tasks
Swagger UI lets you send a POST request with a JSON body directly from your browser. No need to write client code or use a separate tool.
- Return to your browser and open http://127.0.0.1:8000/docs.
- Find the green POST /tasks row and click it to expand.
- Click Try it out in the top right corner of the expanded panel.
- Replace the example JSON in the request body with this:
{
"title": "Test my POST endpoint",
"description": "Creating a task via Swagger UI"
}
- Click Execute.
You should see a response with status code 201 and a JSON body showing your new task with "id": 3 assigned automatically.
- Now scroll up to the GET /tasks row, expand it, click Try it out, then click Execute.
You should see three tasks in the response. The two original sample tasks plus your newly created one.
Why does FastAPI know to read the body as JSON?
When you type-hint a function parameter with a Pydantic model (task_data: TaskCreate), FastAPI knows it is not a path parameter or query parameter. It automatically reads the request body as JSON and validates it against your model.
Getting a 422 Unprocessable Entity error?
This means the JSON body you sent doesn't match the TaskCreate model. Make sure you included a "title" field. The description field is optional, but title is required.
Help me fix my 422 error when creating a task
✔️ 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
Here is the full main.py at this point in the project:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
# --- Data Models ---
class TaskCreate(BaseModel):
title: str
description: str | None = None
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
# --- In-Memory Storage ---
tasks: list[Task] = [
Task(id=1, title="Learn FastAPI", description="Build a REST API project", completed=False),
Task(id=2, title="Buy groceries", description=None, completed=True),
]
next_id: int = 3
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Task Manager API"}
@app.get("/tasks")
def list_tasks() -> list[Task]:
return tasks
@app.get("/tasks/{task_id}")
def get_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.post("/tasks", status_code=201)
def create_task(task_data: TaskCreate) -> Task:
global next_id
new_task = Task(id=next_id, title=task_data.title, description=task_data.description)
tasks.append(new_task)
next_id += 1
return new_task
Your API can now create new tasks on demand. Next up, you'll build the Update and Delete endpoints to complete the full CRUD cycle.
Build the Update and Delete Endpoints
Your API can now create and read tasks. But a real task manager needs to let users modify existing tasks and remove ones they no longer need.
In this step, you'll add the final two CRUD operations: updating a task (PUT) and deleting a task (DELETE). Once these are in place, your API covers the full lifecycle of a task from creation to removal.
In this step, get ready to:
- Build a PUT endpoint that updates only the fields a user sends.
- Build a DELETE endpoint that removes a task from the list.
- Test the full CRUD cycle using Swagger UI.
Create the update endpoint
A PUT request lets a client update an existing resource. Your endpoint needs to accept a task ID in the URL and a request body with the fields to change.
You already have a TaskUpdate model defined with all optional fields. The endpoint will check each field and only apply changes when a value is provided.
- In main.py, scroll to the bottom of the file (below your create_task function) and add the PUT endpoint by pasting this code:
@app.put("/tasks/{task_id}")
def update_task(task_id: int, task_data: TaskUpdate) -> Task:
for task in tasks:
if task.id == task_id:
if task_data.title is not None:
task.title = task_data.title
if task_data.description is not None:
task.description = task_data.description
if task_data.completed is not None:
task.completed = task_data.completed
return task
raise HTTPException(status_code=404, detail="Task not found")
What does this code do?
- @app.put("/tasks/{task_id}") registers this function to handle PUT requests at the specified path. FastAPI extracts task_id from the URL automatically.
- task_data: TaskUpdate tells FastAPI to parse the JSON request body into a TaskUpdate instance. Since all fields in TaskUpdate default to None, the client only needs to send the fields they want to change.
- The three if ... is not None checks ensure the endpoint only updates fields that were explicitly provided in the request body.
- If no task matches the ID, the function raises a 404 error with HTTPException.
- Save main.py.
- Switch back to your browser and open http://127.0.0.1:8000/docs. You should see a new green PUT /tasks/{task_id} section in the Swagger UI page.
Don't see the PUT endpoint in Swagger UI?
- Make sure you saved main.py. The dev server auto-reloads on save, but unsaved changes won't appear.
- Check your terminal for error messages. A syntax error (missing colon, wrong indentation) prevents the server from reloading.
- Refresh the /docs page in your browser. Sometimes the Swagger UI cache needs a hard refresh.
Why isn't my PUT endpoint showing up in the Swagger UI docs?
Create the delete endpoint
The DELETE endpoint removes a task from your list entirely. It uses enumerate() to track the index of each task so it can remove the correct one.
- In main.py, add the DELETE endpoint below your update_task function:
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
for i, task in enumerate(tasks):
if task.id == task_id:
tasks.pop(i)
return {"message": "Task deleted"}
raise HTTPException(status_code=404, detail="Task not found")
What does this code do?
- @app.delete("/tasks/{task_id}") registers this function to handle DELETE requests.
- enumerate(tasks) gives you both the index (i) and the task object as you loop through the list. You need the index to call tasks.pop(i) and remove the correct item.
- The function returns a simple JSON confirmation message on success, or raises a 404 if the task ID does not exist.
- Save main.py.
- Refresh http://127.0.0.1:8000/docs. You should now see a red DELETE /tasks/{task_id} section alongside your other endpoints.
Seeing a server error in your terminal?
- Check that your delete_task function is not accidentally indented inside update_task. Each endpoint function should be at the top level of your file.
- Verify you have a blank line between the end of update_task and the @app.delete decorator.
Help me fix a server error after adding my delete endpoint.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py and that both new endpoints appear in your Swagger UI.
ⓧ I'd like to double check the full code
Here is the complete main.py at this point. Compare yours against it:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
# --- Data Models ---
class TaskCreate(BaseModel):
title: str
description: str | None = None
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
# --- In-Memory Storage ---
tasks: list[Task] = [
Task(id=1, title="Learn FastAPI", description="Build a REST API project", completed=False),
Task(id=2, title="Buy groceries", description=None, completed=True),
]
next_id: int = 3
# --- Endpoints ---
@app.get("/")
def root():
return {"message": "Task Manager API"}
@app.get("/tasks")
def list_tasks() -> list[Task]:
return tasks
@app.get("/tasks/{task_id}")
def get_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.post("/tasks", status_code=201)
def create_task(task_data: TaskCreate) -> Task:
global next_id
new_task = Task(id=next_id, title=task_data.title, description=task_data.description)
tasks.append(new_task)
next_id += 1
return new_task
@app.put("/tasks/{task_id}")
def update_task(task_id: int, task_data: TaskUpdate) -> Task:
for task in tasks:
if task.id == task_id:
if task_data.title is not None:
task.title = task_data.title
if task_data.description is not None:
task.description = task_data.description
if task_data.completed is not None:
task.completed = task_data.completed
return task
raise HTTPException(status_code=404, detail="Task not found")
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
for i, task in enumerate(tasks):
if task.id == task_id:
tasks.pop(i)
return {"message": "Task deleted"}
raise HTTPException(status_code=404, detail="Task not found")
Test the full CRUD cycle
With all four operations in place, you can now walk through the complete lifecycle of a task using the interactive docs.
- Open http://127.0.0.1:8000/docs in your browser.
- Expand the POST /tasks section and click **Try it out**.
- Replace the example JSON body with:
{
"title": "Test CRUD cycle",
"description": "Created to test all operations"
}
- Click **Execute**. You should see a 201 response with your new task (ID 3).
- Expand the PUT /tasks/{task_id} section and click **Try it out**.
- Enter 3 in the task_id field.
- Replace the request body with:
{
"completed": true
}
- Click **Execute**. The response should show your task with "completed": true while the title and description remain unchanged.
- Expand the DELETE /tasks/{task_id} section, click **Try it out**, enter 3 for task_id, and click **Execute**.
- You should see {"message": "Task deleted"} in the response.
- Finally, expand GET /tasks, click **Try it out**, then **Execute**. Confirm that task 3 no longer appears in the list.
What just happened?
You completed a full CRUD cycle: you created a task, updated it to mark it as complete, deleted it, and verified it was gone. This is the fundamental pattern behind almost every web application that manages data.
Getting a 404 when updating or deleting?
- Make sure the task_id you entered matches a task that actually exists. Use GET /tasks first to see the current list and their IDs.
- Remember that your data resets every time the server restarts (since it is stored in memory). If your server reloaded, only the two sample tasks (IDs 1 and 2) exist.
Help me debug why I'm getting a 404 on my update or delete endpoint.
Your REST API is now fully functional. It handles creating, reading, updating, and deleting tasks. You have a complete backend that could power a real task management application.
Secret mission
Add Task Filtering
Your API returns all tasks every time someone calls GET /tasks. Real-world APIs let users narrow down results without creating separate endpoints. Can you add a query parameter that filters tasks by their completion status?
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:
- FastAPI dev server (running process in your terminal).
- Virtual environment (.venv folder inside your project).
- Project folder containing main.py.
Keep everything running
No action needed. Choose this if you're still actively building or experimenting with your API.
- Your FastAPI dev server continues running at http://127.0.0.1:8000.
- Your virtual environment stays activated in your terminal session.
- All project files remain on your Desktop.
Pause - I'll come back to this later
Shut down the running server to free up your terminal. Your files stay on disk so you can pick up where you left off.
- Switch to the terminal where your FastAPI server is running.
- Press Ctrl+C to stop the server.
- Run deactivate to exit the virtual environment.
Coming back later?
To resume, open your project folder in VS Code, activate the virtual environment with source .venv/bin/activate, and run fastapi dev to start the server again.
Delete - I don't want to use this again
Remove all project files and start fresh. This deletes your code, virtual environment, and all installed packages.
- Switch to the terminal where your FastAPI server is running.
- Press Ctrl+C to stop the server.
- Run deactivate to exit the virtual environment.
- Navigate to your Desktop by running:
cd ~/Desktop
- Delete the project folder by running:
rm -rf task-manager-api
What about Python itself?
Python 3.14 and VS Code were already on your machine before this project. You do not need to remove them.
Nice Work!
Nice Work!
You just built a fully functional REST API from scratch. You now understand how web services communicate, how to validate incoming data, and how to structure endpoints for real-world applications.
Here's what you learned:
- Built a REST API server using FastAPI that handles all four CRUD operations (Create, Read, Update, Delete).
- Defined Pydantic data models for automatic request validation and type safety.
- Explored auto-generated interactive API documentation (Swagger UI) to test endpoints without writing client code.
- Added query parameter filtering to search tasks by completion status.
Ready to quiz yourself?