π οΈ Project Refactor β Moving Todos into a Router (Scalable Structure)
In the last session, we created our routers package and added auth.py inside it.
Our main.py file was referencing auth.py.
Now, we are going to properly modularize our project by creating a new file:
π todos.py
π§± Step 1 β Create todos.py
Inside the routers package:
<aside> π‘
you can also create via the terminal using the command
cd routers
ni filename.py
</aside>
Our todos.py will need everything that auth.py has β meaning it will define its own router.
from fastapi import APIRouter
router = APIRouter()Current Structure
Right now, our FastAPI folder contains the virtual environment, and inside it is our actual Python project (TodoApp).
FastAPI
βββ TodoAppProject Structure Recommendation
To prevent module resolution and import path issues, the TodoApp directory should be opened as the root workspace in VS Code, rather than the parent FastAPI directory.
This ensures:
Proper Python module resolution
Clean absolute imports (e.g.,
from database import engine)No
ModuleNotFoundErrorduring executionCorrect working directory when running uvicorn
Once in TodoApp directory, create a new virtual environment and install all dependencies for the project.
Now our TodoApp is going to be the parent location, not the FastAPI directory.
π Step 2 β Move Logic from main.py to todos.py
Now:
π Copy all endpoint logic from main.py
π Paste it inside todos.py
Then make the following changes:
β Remove Unnecessary Items
Remove:
models.Base.metadata.create_all(bind=engine)
app.include_router(auth.router)Remove:
from routers import auth
import modelsRemove: engine
from database import engine, SessionLocalβοΈ Replace FastAPI Instance
from fastapi import FastAPI -> from fastapi import APIRouter
app = FastAPI() -> router = APIRouter()Change all below endpoints
@app -> @routerfrom typing import Annotated
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from fastapi import APIRouter, Path, Depends, HTTPException
from starlette import status
from models import Todos
from database import SessionLocal
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db_dependency = Annotated[Session, Depends(get_db)]
class TodoRequest(BaseModel):
title: str = Field(min_length=3)
description: str = Field(min_length=3, max_length=100)
priority: int = Field(gt=0, lt=6)
complete: bool
@router.get("/", status_code=status.HTTP_200_OK)
async def read_all(db: db_dependency):
return db.query(Todos).all()
@router.get("/todo/{todo_id}", status_code=status.HTTP_200_OK)
async def read_todo(db: db_dependency, todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is not None:
return todo_model
else:
raise HTTPException(status_code=404, detail='Todo Not Found')
@router.post("/todo/", status_code=status.HTTP_201_CREATED)
async def create_todo(db: db_dependency, todo_request: TodoRequest):
todo_model = Todos(**todo_request.model_dump())
db.add(todo_model)
db.commit()
@router.put("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def update_todo(
db: db_dependency,
todo_request: TodoRequest,
todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is None:
raise HTTPException(status_code=404, detail="Todo not found.")
todo_model.title = todo_request.title
todo_model.description = todo_request.description
todo_model.priority = todo_request.priority
todo_model.complete = todo_request.complete
db.commit()
@router.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(
db: db_dependency,
todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is None:
raise HTTPException(status_code=404, detail="Todo not found.")
db.delete(todo_model)
db.commit()π§ Step 3 β Update main.py
Now we make main.py responsible only for:
Creating the FastAPI app
Creating the database
Including routers
Updated main.py
from fastapi import FastAPI
import models
from database import engine
from routers import auth, todos
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
app.include_router(auth.router)
app.include_router(todos.router)π Step 4 β Run the Application
uvicorn main:app --reloadπ Testing in Browser
/auth/β Returns authenticated user/β Returns all todos/todo/{id}β Returns specific todo
Everything still works β but now the structure is clean and scalable.
π¦ What We Achieved (Deep Understanding Section)
Instead of defining endpoints directly inside main.py, we:
π Moved them into routers
π Separated responsibilities
π Improved scalability
π Improved maintainability
Now:
main.pyβ Application startup & router registrationauth.pyβ Authentication logictodos.pyβ Todo logic
This structure allows us to:
Add more modules (users, admin, products, etc.)
Implement authentication & authorization cleanly
Scale the application properly
ποΈ main.py
from fastapi import FastAPI
import models
from database import engine
from routers import auth, todos
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
app.include_router(auth.router)
app.include_router(todos.router)
ποΈ routers/todos.py
from typing import Annotated
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from fastapi import APIRouter, Path, Depends, HTTPException
from starlette import status
from models import Todos
from database import SessionLocal
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db_dependency = Annotated[Session, Depends(get_db)]
class TodoRequest(BaseModel):
title: str = Field(min_length=3)
description: str = Field(min_length=3, max_length=100)
priority: int = Field(gt=0, lt=6)
complete: bool
@router.get("/", status_code=status.HTTP_200_OK)
async def read_all(db: db_dependency):
return db.query(Todos).all()
@router.get("/todo/{todo_id}", status_code=status.HTTP_200_OK)
async def read_todo(db: db_dependency, todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is not None:
return todo_model
else:
raise HTTPException(status_code=404, detail='Todo Not Found')
@router.post("/todo/", status_code=status.HTTP_201_CREATED)
async def create_todo(db: db_dependency, todo_request: TodoRequest):
todo_model = Todos(**todo_request.model_dump())
db.add(todo_model)
db.commit()
@router.put("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def update_todo(
db: db_dependency,
todo_request: TodoRequest,
todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is None:
raise HTTPException(status_code=404, detail="Todo not found.")
todo_model.title = todo_request.title
todo_model.description = todo_request.description
todo_model.priority = todo_request.priority
todo_model.complete = todo_request.complete
db.commit()
@router.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(
db: db_dependency,
todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is None:
raise HTTPException(status_code=404, detail="Todo not found.")
db.delete(todo_model)
db.commit()
No Changes in Other files and Structure