Building Scalable Modular Monoliths with FastAPI: Architecture & Project Structure Standards

amal-babu-git [1]

Modern software systems fail more often because of poor structure than poor code.

When projects grow, inconsistent architecture, unclear module boundaries, and ad-hoc project layouts create technical debt that slows development and scaling. Over time, teams struggle with onboarding, maintenance, and reliability.

To solve this, engineering teams must adopt firm architecture standards and project structure conventions. These standards define how systems are designed, organized, and maintained.

In this post, I will share the system architecture approach and project structure philosophy I follow when building scalable, maintainable software systems—specifically focusing on a Modular Monolith architecture powered by FastAPI. In fact, I’ve open-sourced my exact starting environment, which you can check out on GitHub here: FastAPI Backend Template by Hybrid Interactive.


A Real-World Cautionary Tale

Before diving into the exact standards, I want to share a recent personal experience. My team was tasked with rebuilding a medium-to-large enterprise software application. It was an internal tool with complex, highly interconnected business modules.

The previous external team had chosen a seemingly “fast” approach: a Firebase Backend-as-a-Service (BaaS) paired with an Angular frontend. Relying heavily on AI assistance, they assumed this stack would allow them to rapidly build out the UI and bypass a traditional backend.

At first, the straightforward approach seemed to work. But as the project grew, so did the friction:

  • Unmanaged, highly interconnected dependencies emerged.
  • Modules became tightly coupled.
  • The NoSQL nature of the setup led to a bunch of redundant collections.
  • Data integrity broke down. Features that worked initially began failing seemingly out of nowhere, and updating existing data became a nightmare.

After 6–7 months of development, testing directly on production, and wasting significant time and money, both the client and the agency realized a painful truth: the app wasn’t fixable. The poor choice of tech stack for a complex relational system, combined with a lack of architectural forethought, doomed the project.

That is when my team was brought in to rebuild it. We scrapped the broken system and re-architected it using standard, simple, but robust patterns. We built a modular FastAPI backend, paired with a Next.js frontend, backed by PostgreSQL for relational data integrity, and Redis for caching.

With proper architecture in place, the application was perfectly deployed and continues to run flawlessly in production. It reinforced an important lesson: architecture cannot be an afterthought.


Why Architecture Standards Matter

Software architecture is not just about choosing frameworks or technologies. It is about defining clear boundaries, responsibilities, and system flow.

Without architecture standards:

  • Modules become tightly coupled.
  • Codebases grow unmaintainable as they become “Big Balls of Mud”.
  • Developers implement inconsistent patterns.
  • Scaling the system (both technically and team size) becomes incredibly difficult.

With architecture standards:

  • Teams follow predictable patterns, reducing cognitive load.
  • Codebases remain consistent regardless of who is writing the code.
  • System complexity stays manageable.
  • Development velocity improves over time.

Core Principles of Good Software Architecture

A robust architecture typically follows a few key principles to ensure it stands the test of time.

1. Separation of Concerns

Each layer of the system should have a clearly defined responsibility. Trying to mix HTTP routing logic with database queries is a recipe for disaster.

Typical layers include:

  • Presentation Layer (Routes) – User interfaces or APIs (FastAPI Routers)
  • Application Layer (Services) – Orchestration and use-cases
  • Domain Layer (Schemas/Models) – Business logic and domain rules
  • Infrastructure Layer (CRUD/DB) – External services, database persistence (Repositories), and message brokers.

2. Clear Dependency Direction

Dependencies should flow in a single direction. The domain layer must remain independent of frameworks and infrastructure. Use Dependency Injection (like FastAPI’s Depends()) to pass sessions, user contexts, and config rather than hardcoding imports.

3. Modular System Design (The Modular Monolith based on DDD)

Microservices are heavily pushed as the answer to scalability, but they come with massive operational overhead. A highly effective alternative is the Modular Monolith utilizing Domain-Driven Design (DDD).

Large applications are divided into strict vertical slices, modules, or bounded contexts within a single deployable unit. Files are grouped by feature/domain (e.g. user/, orders/) rather than technical concern (all models in one folder, all routes in another).

The Benefits:

  • Easier development and local testing.
  • No distributed system tax (network latency, eventual consistency headaches).
  • Isolated boundaries per module.
  • Straightforward migration to microservices if and when a specific module actually needs to be scaled independently.

My Standard FastAPI Backend Layout

While FastAPI does not enforce a specific project structure, bringing a structured, feature-driven mindset to it transforms it into an enterprise-grade framework.

Here is the exact production-ready backend architecture layout I use for a modular monolith. It comes packed with async SQLAlchemy 2.0, Celery + Redis background tasks, Prometheus metrics, object storage abstractions, and JWT RBAC:

app/
├── core/                    # Shared infrastructure (never project-specific)
│   ├── main.py              # FastAPI app factory + lifespan
│   ├── settings.py          # Pydantic Settings (env-based config)
│   ├── database.py          # Async SQLAlchemy engine + session
│   ├── models.py            # DeclarativeBase for all models
│   ├── crud.py              # Generic CRUDBase[Model, Create, Update]
│   ├── background/          # Celery app + task infrastructure
│   ├── exception.py         # Global exception handlers
│   └── ...                  # Caching, logging, metrics, etc.

├── apis/
│   └── v1.py                # Aggregates all module routers in one place

├── user/                    # Auth + RBAC vertical module
│   ├── models.py            # User, Role, Permission, RefreshToken
│   ├── seed.py              # Idempotent role/permission seeder
│   ├── auth_management/     # JWT login, refresh, logout
│   ├── permission_management/ # RBAC scoped access helpers
│   ├── crud/                # DB interaction routines
│   ├── schemas/             # Pydantic schemas layout
│   ├── services/            # Business orchestration
│   └── routes/              # FastAPI router endpoints

├── activity/                # Append-only audit log module
├── release_notes/           # Release notes module

└── <your_module>/           # Add new feature modules here (e.g. orders)
    ├── models/              # SQLAlchemy models
    ├── schemas/             # Pydantic schemas
    ├── crud/                # DB CRUD operations
    ├── services/            # Business logic orchestration
    ├── routes/              # FastAPI routers
    ├── dependencies.py      # Dependency injection (Depends() helpers)
    ├── exceptions.py        # Module-specific exceptions
    ├── enums.py             # Domain-specific enums
    └── permissions.py       # Constants for RBAC

Notice how user/ and <your_module>/ represent complete domain features. Rather than having a gigantic models/ folder that creates tight coupling across the entire project, each business domain maintains its own models, schemas, routers, and CRUD operations.


Matching the Frontend Structure

A clean backend deserves a clean frontend. Adopting a feature-driven structure on the frontend prevents UI codebases from turning into a mess of tangled React/Vue components.

src/
├── features/            # Feature-based grouping (Mirroring backend modules)
│   ├── auth/
│   ├── orders/
│   ├── inventory/
│   └── dashboard/

├── components/          # Shared/Global UI components (Buttons, Modals)
├── hooks/               # Global React hooks
├── services/            # API client configurations (Axios/Fetch)
└── utils/               # Helper functions

Final Thoughts

Software architecture is not about achieving academic perfection. It is about building systems that remain maintainable, scalable, and understandable as they evolve in the real world.

By standardizing your project layout and enforcing modular boundaries inside your monolith, you create a system that can grow gracefully.

Architecture standards act as guardrails that help engineering teams build better software systems, allowing developers to focus on solving business problems rather than fighting the codebase.


References & Resources: