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

MethodPathDescription
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}/attachmentsUpload a file
GET/tasks/{id}/attachmentsList files
GET/tasks/{id}/attachments/{file}Download a file
GET/tasks/statsSystem statistics
GET/tasks/export/csvExport tasks as CSV
GET/healthHealth check

WebSocket

PathDescription
/wsReal-time event stream

WebSocket events

EventTrigger
task_createdPOST /tasks/
task_updatedPUT /tasks/{id}
task_completedPATCH with status=completed
task_deletedDELETE /tasks/{id}
attachment_addedPOST /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:

DecisionOptionsTrade-off
Where does the WS broadcast fire?In the router, in a background task, or in a service layerRouter = simple but coupled; service = testable; background = doesn't block the response
What's the upload size limit?5MB, 10MB, 25MBHigher = better UX; lower = less DoS risk
Is the email sent sync or in the background?BackgroundTasks or a direct callBackground 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 broadcastTry/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

CategoryPointsCriteria
Architecture20Modular APIRouter, dependencies, folder structure
CRUD15Every endpoint works correctly
Pydantic Models10Separate models (Create, Update, Patch, Response)
WebSocket15ConnectionManager, broadcast on CRUD operations
Background Tasks10Email simulation, audit log
File Uploads10Upload, validation, download
Middleware5Request logging with timing
Response Models5CSV export, multiple schemas
Error Handling5HTTPException with clear messages
Docs/README5Customized /docs, README with setup
Total100

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

ToolVersionUse
Python3.9+Runtime
FastAPI0.100+Framework
uvicornlatestASGI server
Pydanticv2Validation
python-multipartlatestFile 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:

  1. Capsule 02: Base architecture — models, data, dependencies, routers
  2. Capsule 03: CRUD endpoints + advanced responses + background tasks
  3. 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

  1. FastAPI Tutorial — Quick reference
  2. FastAPI Advanced — Advanced features
  3. Pydantic v2 Docs — Models and validation
  4. 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.