Building Your First API — Task Manager (Part 1)¶
🎯 What You’ll Build¶
A simple Task Manager API with endpoints to:
- ✅ Create a task
- 📖 Read tasks (single and all)
- ✏️ Update a task
- ❌ Delete a task
You’ll use:
- Pydantic v2 models for request and response validation
- FastAPI routing and error handling
- A Python dictionary as an in-memory database
- Swagger UI for interactive testing
🧠 Step 1: Project Setup¶
Create a file called main.py and install FastAPI and Uvicorn:
pip install fastapi uvicorn
Run the server:
uvicorn main:app --reload
📦 Step 2: Define the Task Model¶
Use Pydantic v2 to define the structure of a task.
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class Task(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False
🗃️ Step 3: Create an In-Memory Data Store¶
Use a dictionary to simulate a database. Each task will have a unique ID.
tasks = {}
task_id_counter = 1
✅ Step 4: Create a Task (POST /tasks)¶
@app.post("/tasks", status_code=201)
def create_task(task: Task):
global task_id_counter
task_data = task.model_dump() # Dump the request body into a dictionary
task_data["id"] = task_id_counter # Add an id for unique identification
tasks[task_id_counter] = task_data # Simulate adding a record to database
task_id_counter += 1 # Increment counter for adding another task the next time this request is made
return task_data
model_dump()is the correct way to convert a Pydantic v2 model to a dictionary.- Assigns a unique ID and stores the task.
🧪 Example request:
{
"title": "Buy milk",
"description": "From the store",
"completed": false
}
📖 Step 5: Read All Tasks (GET /tasks)¶
@app.get("/tasks")
def get_all_tasks():
return list(tasks.values())
Returns a list of all tasks.
📖 Step 6: Read a Single Task by ID (GET /tasks/{task_id})¶
from fastapi import HTTPException
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
return tasks[task_id]
Returns a single task or a 404 error if not found.
✏️ Step 7: Update a Task (PUT /tasks/{task_id})¶
@app.put("/tasks/{task_id}")
def update_task(task_id: int, updated_task: Task):
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
task_data = updated_task.model_dump()
task_data["id"] = task_id
tasks[task_id] = task_data
return task_data
Replaces the entire task with new data.
❌ Step 8: Delete a Task (DELETE /tasks/{task_id})¶
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
del tasks[task_id]
Deletes the task and returns a 204 No Content status.
📚 Step 9: Explore Swagger UI¶
FastAPI automatically generates interactive API documentation using Swagger UI.
🧭 Visit:
http://127.0.0.1:8000/docs→ Swagger UIhttp://127.0.0.1:8000/redoc→ ReDoc (alternative view)
With Swagger UI, you can:
- See all endpoints
- View request and response schemas
- Try out requests directly in the browser
This is invaluable for testing and debugging.
🧠 Recap¶
You now have a working CRUD API:
| Method | Endpoint | Action |
|---|---|---|
| POST | /tasks |
Create a task |
| GET | /tasks |
Read all tasks |
| GET | /tasks/{id} |
Read one task |
| PUT | /tasks/{id} |
Update a task |
| DELETE | /tasks/{id} |
Delete a task |
All endpoints are:
- Type-safe
- Validated via Pydantic v2
- Documented automatically
Your code should now be like this:
from fastapi import FastAPI
from fastapi import HTTPException
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class Task(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False
tasks = {}
task_id_counter = 1
@app.post("/tasks", status_code=201)
def create_task(task: Task):
global task_id_counter
task_data = task.model_dump()
task_data["id"] = task_id_counter
tasks[task_id_counter] = task_data
task_id_counter += 1
return task_data
@app.get("/tasks")
def get_all_tasks():
return list(tasks.values())
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
return tasks[task_id]
@app.put("/tasks/{task_id}")
def update_task(task_id: int, updated_task: Task):
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
task_data = updated_task.model_dump()
task_data["id"] = task_id
tasks[task_id] = task_data
return task_data
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
del tasks[task_id]
🧪 Practice Challenge¶
Extend the API with:
- A query parameter to filter tasks by
completed=true - A PATCH endpoint to toggle completion status
- A response model that hides internal fields (e.g. future
owner_id)