As a FastAPI application grows, organizing everything into global folders such as routers, schemas, services, and models can become difficult to maintain.
A feature-based architecture groups code by business domain rather than technical responsibility. This makes features easier to understand, develop, test, and maintain independently.
A typical feature-based FastAPI application might look like this:
app/├── main.py├── cli.py├── api/│ └── router.py├── core/│ ├── config.py│ ├── database.py│ └── security.py├── db/│ ├── base.py│ └── registry.py├── shared/│ └── bot_protection/├── data/│ └── faqs.json└── features/├── auth/├── admins/├── categories/├── plants/├── customers/├── orders/├── payments/├── reviews/├── notifications/├── overview/├── knowledge/└── chat/
Each top-level directory has a clear responsibility, while features/ contains the application’s business functionality.
main.pymain.py initializes the FastAPI application and configures application-wide behavior.
Typical responsibilities include:
Keep this file focused on application initialization rather than business logic.
api/The API layer combines routers from individual features into a single application-level router.
main.py↓api/router.py↓Feature routers├── plants├── orders├── customers└── reviews
For example:
# app/api/router.pyfrom fastapi import APIRouterfrom app.features.plants.router import router as plants_routerfrom app.features.orders.router import router as orders_routerapi_router = APIRouter()api_router.include_router(plants_router,prefix="/plants",tags=["Plants"],)api_router.include_router(orders_router,prefix="/orders",tags=["Orders"],)
The main application can then register api_router once, keeping route registration centralized.
core/The core/ directory contains application-wide infrastructure that is not specific to a business feature.
Examples include:
config.py: Environment variables and application settings.database.py: Database engine and session configuration.security.py: Shared security primitives.cookies.py: Cookie configuration and utilities.sse.py: Shared Server-Sent Events utilities.text.py: General text normalization utilities.The dependency direction matters: features can use core infrastructure, but core modules should not import business features. This helps prevent circular imports and keeps foundational code reusable.
db/The db/ directory contains shared SQLAlchemy infrastructure.
For example, base.py defines the declarative base and common model mixins:
# app/db/base.pyfrom sqlalchemy.orm import DeclarativeBaseclass Base(DeclarativeBase):pass
A model registry can import models from every feature so that SQLAlchemy and Alembic can discover the complete schema.
# app/db/registry.pyfrom app.features.categories.models import Categoryfrom app.features.plants.models import Plantfrom app.features.orders.models import Order__all__ = ["Category", "Plant", "Order"]
This is a simplified example; the registry should import every model that belongs in the database schema.
Keep the distinction clear:
db/ contains shared database infrastructure.features/*/models.py contains feature-specific ORM models.shared/The shared/ directory contains reusable functionality used by multiple business features.
For example, bot protection might be used by both orders and reviews:
shared/└── bot_protection/├── dependencies.py└── service.py
Neither feature should own this functionality if both depend on it.
However, avoid turning shared/ into a miscellaneous folder. Move code there when multiple features genuinely need it and its responsibility is clear.
features/This is the central part of the architecture.
Each feature owns the code related to a particular business domain. For example:
features/plants/├── router.py├── public_router.py├── service.py├── schemas.py├── models.py└── dependencies.py
Each module has a distinct responsibility:
| Module | Responsibility |
|---|---|
router.py | Defines feature endpoints and HTTP behavior |
public_router.py | Defines public-facing endpoints when separate routing is useful |
service.py | Implements business logic |
schemas.py | Defines Pydantic request and response schemas |
models.py | Defines SQLAlchemy ORM models |
dependencies.py | Defines reusable feature-specific dependencies |
The typical request flow is:
HTTP Request↓Router↓Service↓Database Session / ORM Models↓Database
Schemas define the data contract between the API and its clients, while dependencies provide reusable components such as authenticated users or database sessions.
Not every feature needs every file. Start with the modules a feature actually requires instead of creating empty files for consistency.
Features may need to communicate with one another. For example, the orders feature may need to check plant availability before creating an order.
A maintainable approach is to expose clear service functions or interfaces rather than importing another feature’s internal implementation details.
Orders Feature↓Plants Service Interface↓Plant Availability
For larger applications, explicit interfaces or application-level orchestration can reduce coupling between features.
Avoid circular dependencies such as plants importing orders while orders also imports plants. Shared abstractions or a coordinating service can help break these cycles.
Feature-based organization is especially useful when:
For a small API, a simple structure with a few modules may be sufficient. Architecture should reduce complexity rather than introduce unnecessary folders and abstractions.
core/ and db/ provide shared infrastructure.api/ aggregates feature routers.shared/ contains genuinely reusable cross-feature functionality.features/ owns business logic, schemas, ORM models, and routes.The goal is not to create the most elaborate folder structure. It is to make each business feature easy to locate, understand, test, and change without unnecessarily affecting the rest of the application.