Skip to content

Scaling Up: Structuring Bigger Applications

Scaling Up: Structuring Bigger Applications

Section titled “Scaling Up: Structuring Bigger Applications”

Writing everything in a single main.py file is fine for a “Hello World” app, but as your application grows, this becomes unmanageable. Imagine a book where all chapters are written on a single, infinitely long page. Hard to read, right? In this chapter, we will learn how to organize our code into a professional directory structure.

We should separate our logic into modules. A common pattern in FastAPI is to have a dedicated folder (package) for your application logic and a routers subdirectory for your endpoints.

.
├── app
│ ├── __init__.py
│ ├── main.py # The entry point
│ ├── dependencies.py # Shared dependencies
│ └── routers # Separate modules for path operations
│ ├── __init__.py
│ ├── items.py # Endpoints related to items
│ └── users.py # Endpoints related to users

The APIRouter class is the key to this separation. It works exactly like the FastAPI class (you can decorate paths with @router.get, etc.), but it is designed to be included in the main application later. Think of it as a “mini-application”.

Here is an example of app/routers/users.py:

from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
def read_user_me():
return {"username": "fakecurrentuser"}

Once you have defined your routers, you need to bring them together in your app/main.py. We use the .include_router() method.

You can also use this opportunity to define global configurations for a specific router, such as a URL prefix or tags for the automatic documentation.

from fastapi import FastAPI
from .routers import users, items
app = FastAPI()
# Include the users router
# All paths in 'users' will naturally start with /users due to the router definition,
# but we can also enforce prefixes here if we want.
app.include_router(users.router)
# Include the items router with a prefix
# If items.py has a path "/", it becomes "/items/"
app.include_router(
items.router,
prefix="/items",
tags=["items"],
responses={404: {"description": "Not found"}},
)

By structuring your application this way, multiple developers can work on different modules simultaneously without conflicts, and your code remains clean and navigable.