Modularizing Your FastAPI App¶
🎯 What You’ll Learn¶
- Why modularization matters in real-world APIs
- How to use
APIRouterto split your app into logical components - How to organize your FastAPI project into files and folders
- How to register routers in the main app
🧠 Why Modularize?¶
As your app grows, a single main.py file becomes unmanageable. Modularization helps you:
- Separate concerns (e.g., users, tasks, auth)
- Reuse logic across endpoints
- Improve readability and testability
- Scale to production-grade architecture
✋ Before we begin¶
We will use some HTTP response status codes in the following sections. If you are not familiar with them, you can find a comprehensive list of status codes with their explanations on MDN: HTTP response status codes - HTTP | MDN
🧩 Step 1: Understand APIRouter¶
FastAPI provides APIRouter to define routes in isolated modules. Each router behaves like a mini FastAPI app that can be mounted into the main app.
🗂️ Step 2: Restructure Your Project¶
Let’s refactor the Task Manager API into a modular layout:
task_manager/
├── main.py
├── models/
│ └── task.py
├── routers/
│ └── tasks.py
└── __init__.py
📦 Step 3: Define the Task Model¶
📄 models/task.py
from pydantic import BaseModel
from typing import Optional
class Task(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False
🔀 Step 4: Create the Task Router¶
📄 routers/tasks.py
from fastapi import APIRouter, HTTPException
from models.task import Task
router = APIRouter(prefix="/tasks", tags=["Tasks"])
# In-memory store
tasks = {}
task_id_counter = 1
@router.post("/", 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
@router.get("/")
def get_all_tasks():
return list(tasks.values())
@router.get("/{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]
@router.put("/{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
@router.delete("/{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]
prefix="/tasks"means all routes start with/taskstags=["Tasks"]groups them in Swagger UI
🚀 Step 5: Register the Router¶
📄 main.py
from fastapi import FastAPI
from routers import tasks
app = FastAPI()
app.include_router(tasks.router)
include_router()mounts the task router into the main app- Now all task routes are available under
/tasks
📚 Step 6: Swagger UI Still Works¶
Visit http://127.0.0.1:8000/docs and you’ll see:
- All your task routes grouped under the “Tasks” tag
- Full request/response schemas
- Interactive testing still works
🧠 Recap¶
You now have a modular FastAPI app:
- Models live in
models/ - Routes live in
routers/ - The main app just wires everything together
This structure is scalable, testable, and production-ready.
🧪 Practice Challenge¶
Add a new router:
- Create
routers/ping.pywith a singleGET /pingroute that returns{"pong": true} - Register it in
main.py - Confirm it appears in Swagger UI under a new tag