Why Do We Need Project Organization?
Chapter 1: Why Do We Need Project Organization?
Welcome to Lesson 16 of our Python and FastAPI course. In the previous lessons, you built a simple CRUD API for tasks. All your code was likely in a single file, perhaps main.py. This works for small examples, but as your project grows, a single file becomes messy, hard to read, and difficult to maintain. In this chapter, you will learn why project organization is essential and how to structure a professional FastAPI project from the start.
What is Project Organization?
Project organization means arranging your code, files, and folders in a logical and consistent way. Instead of putting everything in one file, you split your code into separate modules and packages based on their responsibility. For example, you might have one folder for database models, another for API routes, and another for configuration.
Why Do We Need It?
Here are the main reasons why organizing your FastAPI project is critical, even for a beginner:
- Readability: When your code is split into small, focused files, it is much easier to read and understand. You don't have to scroll through hundreds of lines to find a specific function.
- Maintainability: As you add new features or fix bugs, you can work on one small file without breaking other parts of the application.
- Reusability: Well-organized code allows you to reuse components (like database models or utility functions) in other projects.
- Collaboration: If you work with a team, a clear structure helps everyone know where to put their code and how to find existing code.
- Scalability: A good structure makes it easier to add new endpoints, new database tables, or even switch to a different database later.
Common Problems Without Organization
Let's look at a typical messy FastAPI project. Imagine you have a single main.py file that contains:
- All imports
- Database connection setup
- Pydantic models
- All API route functions
- Error handlers
- Configuration variables
This file quickly becomes hundreds of lines long. If you need to change the database URL, you have to search through the entire file. If you want to add a new endpoint, you risk accidentally modifying an existing one. This is not professional and leads to errors.
How to Organize a FastAPI Project
We will follow a standard structure that is widely used in the FastAPI community. Here is the folder layout we will build:
my_fastapi_project/
│
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── database.py
│ ├── models.py
│ ├── schemas.py
│ ├── crud.py
│ └── routers/
│ ├── __init__.py
│ └── tasks.py
│
├── requirements.txt
└── .env
Let's explain each file and folder:
app/: This is the main package for your application. All your code lives inside this folder.__init__.py: An empty file that tells Python that theappfolder is a package. It can also be used to import things from submodules.main.py: The entry point of your application. It creates the FastAPI instance and includes the routers. This file should be very short.config.py: Contains configuration settings, like database URL, secret keys, etc. It usually reads from environment variables.database.py: Handles the database connection and session management.models.py: Defines your database models (SQLAlchemy models).schemas.py: Defines your Pydantic schemas for request and response validation.crud.py: Contains the database operations (Create, Read, Update, Delete) as functions.routers/: A folder containing separate route files. Each file handles a group of related endpoints (e.g., tasks, users).requirements.txt: Lists all Python packages your project depends on..env: Stores environment variables (like database password) – never commit this file to version control.
Practical Example: Refactoring Our Tasks API
Let's take the simple CRUD API from Lesson 15 and refactor it into this organized structure. We will create each file step by step.
Step 1: Create the folder structure
Create a new folder called my_fastapi_project. Inside it, create the app folder and the routers subfolder. Also create empty __init__.py files inside app and routers.
Step 2: Create config.py
# app/config.py
import os
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./tasks.db")
# You can add more configuration variables here
Step 3: Create database.py
# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .config import DATABASE_URL
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Step 4: Create models.py
# app/models.py
from sqlalchemy import Column, Integer, String, Boolean
from .database import Base
class Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, default="")
completed = Column(Boolean, default=False)
Step 5: Create schemas.py
# app/schemas.py
from pydantic import BaseModel
class TaskCreate(BaseModel):
title: str
description: str = ""
completed: bool = False
class TaskResponse(TaskCreate):
id: int
class Config:
orm_mode = True
Step 6: Create crud.py
# app/crud.py
from sqlalchemy.orm import Session
from . import models, schemas
def get_tasks(db: Session):
return db.query(models.Task).all()
def create_task(db: Session, task: schemas.TaskCreate):
db_task = models.Task(**task.dict())
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
Step 7: Create routers/tasks.py
# app/routers/tasks.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import schemas, crud
from ..database import get_db
router = APIRouter(prefix="/tasks", tags=["tasks"])
@router.get("/", response_model=list[schemas.TaskResponse])
def read_tasks(db: Session = Depends(get_db)):
return crud.get_tasks(db)
@router.post("/", response_model=schemas.TaskResponse)
def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):
return crud.create_task(db, task)
Step 8: Create main.py
# app/main.py
from fastapi import FastAPI
from .database import engine, Base
from .routers import tasks
Base.metadata.create_all(bind=engine)
app = FastAPI(title="Organized Tasks API")
app.include_router(tasks.router)
Step 9: Create requirements.txt
fastapi
uvicorn
sqlalchemy
pydantic
python-dotenv
Now you can run the application from the my_fastapi_project folder using:
uvicorn app.main:app --reload
Common Mistakes Beginners Make
- Forgetting
__init__.py: Without these files, Python will not recognize your folders as packages, and imports will fail. - Circular imports: Be careful when importing between modules. For example,
schemas.pyshould not import fromrouters. Keep imports one-directional. - Putting too much in
main.py: Themain.pyshould only create the app and include routers. All logic belongs in other files. - Hardcoding configuration: Never hardcode database URLs or secret keys. Use environment variables and a
.envfile.
Practice Task
Now it's your turn. Take the simple CRUD API you built in Lesson 15 (or use the example above) and refactor it into the organized structure we discussed. Make sure you have at least the following files:
app/main.pyapp/config.pyapp/database.pyapp/models.pyapp/schemas.pyapp/crud.pyapp/routers/tasks.pyrequirements.txt
Run the application and test it using Swagger UI at http://127.0.0.1:8000/docs. If everything works, you have successfully organized your first professional FastAPI project!

Loading ratings...