A package is a directory containing Python modules, marked as a package with an __init__.py file. Packages let you organise a large codebase into logical namespaces โ app.routers, app.models, app.schemas โ so that related code lives together and can be imported cleanly. A well-structured FastAPI project is a package (or a set of packages) where the layout reflects the application architecture. Getting the project structure right from the start saves significant refactoring later and makes the codebase easy for new team members to navigate.
Creating a Package
# A package is a directory with __init__.py
# app/
# __init__.py โ makes 'app' a package
# main.py
# config.py
# database.py
# routers/
# __init__.py โ makes 'routers' a sub-package
# posts.py
# users.py
# auth.py
# models/
# __init__.py
# post.py
# user.py
# comment.py
# schemas/
# __init__.py
# post.py
# user.py
# services/
# __init__.py
# post_service.py
# email_service.py
# utils/
# __init__.py
# security.py
# pagination.py
# Imports from nested packages
from app.routers import posts
from app.models.post import Post
from app.schemas.user import UserCreate, UserResponse
from app.services.email_service import send_welcome_email
__init__.py is optional โ directories without it are treated as namespace packages. However, for FastAPI projects, always include __init__.py files. They make the package structure explicit, allow you to control what is exported from each package, and ensure compatibility with all tools (pytest, mypy, linters). An empty __init__.py file is perfectly fine โ you only need to add code to it when you want to simplify imports.__init__.py to create convenient import shortcuts. If app/schemas/__init__.py contains from .post import PostCreate, PostResponse and from .user import UserCreate, UserResponse, then other modules can write from app.schemas import PostCreate, UserResponse instead of the longer from app.schemas.post import PostCreate. This pattern is called re-exporting and makes the public API of a package clear and concise.main.py or a flat collection of files. A flat structure becomes unmaintainable quickly โ 500-line files with mixed concerns (database models, request schemas, route handlers, and utilities all in one file) are extremely hard to test, review, and maintain. Structure your project by concern from day one: routers together, models together, schemas together.__init__.py โ Package Initialisation and Re-exports
# app/schemas/__init__.py โ re-export for clean imports
from .post import PostCreate, PostUpdate, PostResponse
from .user import UserCreate, UserUpdate, UserResponse, UserLogin
from .comment import CommentCreate, CommentResponse
# Now external code can import from the package:
from app.schemas import PostCreate, UserResponse
# Instead of the verbose:
from app.schemas.post import PostCreate
from app.schemas.user import UserResponse
# app/__init__.py โ version information
__version__ = "1.0.0"
__author__ = "Your Name"
# app/models/__init__.py โ register all models so SQLAlchemy sees them
from .user import User # noqa: F401
from .post import Post # noqa: F401
from .comment import Comment # noqa: F401
# The noqa comment suppresses "unused import" lint warnings
# SQLAlchemy needs all models imported before create_all() is called
Recommended FastAPI Project Layout
# Production FastAPI project structure
# blog-api/
# app/
# __init__.py
# main.py โ FastAPI app creation, middleware, router inclusion
# config.py โ Settings class (pydantic-settings)
# database.py โ SQLAlchemy engine, session, Base
# dependencies.py โ Common FastAPI Depends() functions
# routers/
# __init__.py
# auth.py
# posts.py
# users.py
# comments.py
# models/ โ SQLAlchemy ORM models
# __init__.py
# user.py
# post.py
# comment.py
# schemas/ โ Pydantic request/response models
# __init__.py
# user.py
# post.py
# comment.py
# services/ โ Business logic (called by routers)
# __init__.py
# auth_service.py
# post_service.py
# email_service.py
# utils/
# __init__.py
# security.py โ password hashing, JWT
# pagination.py
# email.py
# tests/
# conftest.py
# test_auth.py
# test_posts.py
# alembic/ โ database migrations
# .env
# requirements.txt
# requirements-dev.txt
# pyproject.toml
app/main.py โ The Entry Point
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.routers import auth, posts, users, comments
app = FastAPI(
title = settings.app_name,
description = "A full-featured blog API built with FastAPI",
version = "1.0.0",
docs_url = "/docs", # Swagger UI
redoc_url = "/redoc", # ReDoc
)
# โโ Middleware โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app.add_middleware(
CORSMiddleware,
allow_origins = settings.allowed_origins,
allow_credentials = True,
allow_methods = ["*"],
allow_headers = ["*"],
)
# โโ Routers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(posts.router, prefix="/api/posts", tags=["posts"])
app.include_router(users.router, prefix="/api/users", tags=["users"])
app.include_router(comments.router, prefix="/api/comments", tags=["comments"])
@app.get("/api/health", tags=["health"])
def health_check():
return {"status": "ok", "version": "1.0.0"}
Common Mistakes
Mistake 1 โ Flat structure that does not scale
โ Wrong โ all code in one file:
# main.py โ 1200 lines with models, schemas, routes, and utils all mixed
from sqlalchemy import Column, Integer, String
from pydantic import BaseModel
# ... everything in one file
โ Correct โ separate concerns into packages from the start.
Mistake 2 โ Missing __init__.py causing import failures
โ Wrong โ directory without __init__.py treated as namespace package:
# app/routers/ has no __init__.py
from app.routers.posts import router # may fail in some tool configurations
โ Correct โ always include __init__.py in every package directory.
Mistake 3 โ Importing models in wrong order causing SQLAlchemy issues
โ Wrong โ relationships undefined because model not imported yet:
# database.py calls Base.metadata.create_all(engine)
# But User model never imported โ table not created!
โ Correct โ import all models before create_all():
from app.models import User, Post, Comment # โ all imported
Base.metadata.create_all(engine)
Quick Reference
| Concept | Detail |
|---|---|
| Package marker | __init__.py in directory |
| Re-export from package | from .module import Name in __init__.py |
| Package version | __version__ = "1.0.0" in __init__.py |
| Import from sub-package | from app.routers.posts import router |
| Include router | app.include_router(router, prefix="/api/posts") |