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