Module 6: Project — Advanced Task Manager API
Final Project: Advanced Task Manager API
Project overview
This is the capstone module of the FastAPI Advanced Features guide. Across 5 modules you learned dependency injection, APIRouter, advanced response models, background tasks, WebSockets, and file uploads — each one on its own. Now you combine all of them into a single professional project.
The Task Manager API is a complete task management API with:
- Modular architecture with APIRouter and dependency injection
- Real-time notifications over WebSocket
- Background processing for emails and cleanup
- File uploads to attach documents to tasks
- Multiple response schemas depending on the context
- Middleware for logging and timing
- Professional documentation at /docs
This isn't a step-by-step tutorial. It's a project with specifications where you decide how to implement each part, using everything you learned. The capsules that follow walk you through the architecture, but the challenge is yours.
By the end of this module you'll have a portfolio-worthy project that shows mastery of advanced FastAPI: testable architecture, real-time, async, with professional documentation. It's the second portfolio piece of the path (the first one is in FastAPI Fundamentals).
What you'll build
HTTP endpoints
| Method | Path | Description |
|---|---|---|
| POST | /tasks/ | Create a task (with a WS notification + background email) |
| GET | /tasks/ | List tasks (with filters and pagination) |
| GET | /tasks/{id} | Task detail (with attachments) |
| PUT | /tasks/{id} | Full task update |
| PATCH | /tasks/{id} | Partial update |
| DELETE | /tasks/{id} | Delete a task |
| POST | /tasks/{id}/attachments | Upload a file |
| GET | /tasks/{id}/attachments | List files |
| GET | /tasks/{id}/attachments/{file} | Download a file |
| GET | /tasks/stats | System statistics |
| GET | /tasks/export/csv | Export tasks as CSV |
| GET | /health | Health check |
WebSocket
| Path | Description |
|---|---|
| /ws | Real-time event stream |
WebSocket events
| Event | Trigger |
|---|---|
| task_created | POST /tasks/ |
| task_updated | PUT /tasks/{id} |
| task_completed | PATCH with status=completed |
| task_deleted | DELETE /tasks/{id} |
| attachment_added | POST /tasks/{id}/attachments |
Architecture
task-manager-api/
├── app/
│ ├── __init__.py
│ ├── main.py # App config, middleware, lifespan
│ ├── models.py # Pydantic models (Task, User, etc.)
│ ├── data.py # In-memory storage + helpers
│ ├── dependencies.py # DI: pagination, task lookup, etc.
│ ├── websocket_manager.py # ConnectionManager
│ ├── routers/
│ │ ├── __init__.py
│ │ ├── tasks.py # CRUD + stats + export
│ │ ├── uploads.py # File upload/download
│ │ └── websocket.py # WS endpoint
│ ├── middleware/
│ │ ├── __init__.py
│ │ └── logging.py # Request logging + timing
│ ├── background/
│ │ ├── __init__.py
│ │ └── notifications.py # Email simulation + audit
│ └── uploads/ # Stored files
├── requirements.txt
└── README.md
How the modules connect
HTTP Request ─────────────────────────────────────►
│ │
├── Middleware (M2): logging, timing ───────────►│
│ │
├── Dependencies (M1): pagination, lookup ─────►│
│ │
├── Response Models (M3): schemas per context ──│
│ │
├── Background Tasks (M4): email, cleanup ─────►│ (async)
│ │
├── WebSocket (M5): broadcast notification ────►│ (real-time)
│ │
└── File Uploads (M5): save + validate ────────►│
Design decisions you're going to make
This isn't a project where you "follow the steps." There are real decisions to make and justify:
| Decision | Options | Trade-off |
|---|---|---|
| Where does the WS broadcast fire? | In the router, in a background task, or in a service layer | Router = simple but coupled; service = testable; background = doesn't block the response |
| What's the upload size limit? | 5MB, 10MB, 25MB | Higher = better UX; lower = less DoS risk |
| Is the email sent sync or in the background? | BackgroundTasks or a direct call | Background doesn't block the POST; direct is simpler but degrades response time |
| Is the CSV export streamed or built in memory? | StreamingResponse or Response(content=...) | Stream for big datasets; memory for small cases |
| What happens if a WS client disconnects mid-broadcast? | Try/except per client or fail the whole broadcast | Try/except per client is the standard practice |
These decisions are the difference between "code that works" and "code a team can maintain." Document your choices in the README — it's as much a part of the deliverable as the code.
Grading rubric
| Category | Points | Criteria |
|---|---|---|
| Architecture | 20 | Modular APIRouter, dependencies, folder structure |
| CRUD | 15 | Every endpoint works correctly |
| Pydantic Models | 10 | Separate models (Create, Update, Patch, Response) |
| WebSocket | 15 | ConnectionManager, broadcast on CRUD operations |
| Background Tasks | 10 | Email simulation, audit log |
| File Uploads | 10 | Upload, validation, download |
| Middleware | 5 | Request logging with timing |
| Response Models | 5 | CSV export, multiple schemas |
| Error Handling | 5 | HTTPException with clear messages |
| Docs/README | 5 | Customized /docs, README with setup |
| Total | 100 |
Levels:
- 90-100: Excellent — ready for your portfolio
- 75-89: Good — functional with minor details
- 60-74: Acceptable — works but needs polish
- <60: Needs work — review the earlier modules
Technologies
| Tool | Version | Use |
|---|---|---|
| Python | 3.9+ | Runtime |
| FastAPI | 0.100+ | Framework |
| uvicorn | latest | ASGI server |
| Pydantic | v2 | Validation |
| python-multipart | latest | File uploads |
requirements.txt
fastapi>=0.100.0
uvicorn[standard]
python-multipart
Common traps in capstone projects
1. Wanting to "use every feature" where they don't apply
You have WebSocket in the toolkit, but that doesn't mean every event should be broadcast. Do clients really need to know in real time that a file was downloaded? Probably not. Put WS where it adds value (task state changes), not as a demo of the feature.
How to spot it: the WS clients receive more events than they render. How to fix it: list which events change the client's UI, and broadcast only those.
2. Coupling the WebSocket broadcast to the router
If your POST /tasks/ calls manager.broadcast(...) directly, the router ends up coupled to the WebSocket. If tomorrow you move to Redis pub/sub or queues, you rewrite the router. Better: a notify_task_event(event, payload) function that the router calls; the ConnectionManager gets injected as a dependency.
How to spot it: your router grows to 200 lines and mixes business logic with I/O. How to fix it: extract the notification into a helper or a service layer; inject the manager as a dependency.
3. Not documenting your limitations honestly
In-memory data, no auth, uploads on the local filesystem — these are all real limitations of the project. Hiding them in the README is weakness. Documenting them with "this version doesn't use X because it covers FastAPI Advanced features; persistence arrives in the PostgreSQL & SQLAlchemy guide" is senior thinking.
How to spot it: a reviewer asks "what happens if I restart the server?" and you don't have a documented answer. How to fix it: a "Known limitations & roadmap" section in the README, bridging to the guides that follow.
4. Background tasks that fail silently
FastAPI's BackgroundTasks has no retry and no dead letter queue. If your email task fails, the log may not capture it. In this project that's acceptable (it's a demo), but your README should say so: "for production, use Celery or a real queue."
How to spot it: your "I create a task, I get a simulated email in the log" test passes, but sometimes the log doesn't show up. How to fix it: add try/except + a log inside the background task; document the limitation in the README.
How to use the capsules that follow
Capsules 02-04 walk you through the implementation in 3 phases:
- Capsule 02: Base architecture — models, data, dependencies, routers
- Capsule 03: CRUD endpoints + advanced responses + background tasks
- Capsule 04: WebSockets + uploads + middleware
Capsule 05 has the full verification, the rubric, the final code, and the delivery.
Recommendation: try implementing each phase on your own before reading the reference code. Go back to the capsules from earlier modules if you need to refresh a concept.
Self-check: are you ready to start the project?
Before moving to capsule 02, make sure these three things are clear to you:
1. Why shouldn't the WS broadcast live inside the tasks router?
Because it couples HTTP logic with real-time logic. If tomorrow you switch from WS to SSE, queues, or webhooks, you rewrite the router.
The standard practice: the ConnectionManager gets injected as a dependency, and the router asks it to "notify this event" without knowing how it's delivered. That's the same thing that separates services in other stacks (Spring, NestJS).
2. Which design decision has the biggest impact on testability?
Dependency injection. Without DI, tests need global mocks or monkey-patching of the module. With DI, tests use app.dependency_overrides to inject fakes (an in-memory dict as the "DB", a fake ConnectionManager).
That's the reason dependency_injection is module 1 of this guide — without it, the rest of the features aren't testable.
3. You have to choose whether the email background task runs before or after the WS broadcast. Which one first?
WS broadcast first. The broadcast is synchronous inside the request's coroutine — if you put it after the background scheduling, the WS client gets the event with no delay. The email can take seconds (background task), and it shouldn't block the confirmation to the user.
General pattern: in-memory notifications first, slow I/O afterwards.
If answering these three comes easy, you're ready to start on the architecture.
Prerequisites
- Modules 1-5 completed
pip install fastapi uvicorn[standard] python-multipart- An
app/uploads/directory created - A terminal with uvicorn working
Resources
- FastAPI Tutorial — Quick reference
- FastAPI Advanced — Advanced features
- Pydantic v2 Docs — Models and validation
- Starlette — The underlying framework
Summary and next step
- You're going to build a Task Manager API that combines the 6 features of the guide into a portfolio-worthy project.
- The design decisions you make (where to broadcast, how to separate concerns, what to document) are as much a part of the deliverable as the code.
- The rubric rewards architecture and testability over the number of features.
- The documented limitations are bridges to the guides that follow — PostgreSQL, Auth, Docker.
Checkpoint: before moving on, you should be able to explain why this project does NOT use a real database or authentication, and which guides in the path solve each limitation.
Bridge to the next capsule: you've seen the "what" of the project — endpoints, events, rubric. Capsule 02 takes on the architectural "how": defining the Pydantic models split by operation, wiring up the ConnectionManager, and structuring the dependencies that make everything testable. Without that foundation, adding WebSockets and uploads in later capsules turns into a patch job — with it, they're pieces that fit cleanly.
What's next?
In Capsule 02 you define the base architecture: Pydantic models, in-memory data, reusable dependencies, and the router structure.