FastAPI for Django Programmers: The Freedom Trap and the Path to Clean Architecture

I’ve been coding Python and Django for an embarrassingly long time. One thing I’ve always appreciated about Django is the nicely laid out structure it provides right out of the box. FastAPI, however, is a different beast altogether. It is a micro-framework of sorts, which means it doesn’t enforce a specific project layout.
Recently, I’ve been tasked with taking over multiple FastAPI projects, and in nearly every case, I run into the same set of issues. The code isn’t scalable, and the logic is so tangled that the clients are essentially stuck. While FastAPI is marketed as “fast to code” and “easy to learn,” that very freedom is a trap that often leads to unmaintainable “spaghetti code”.
The Cost of Total Freedom: Bad FastAPI Structures
The most common mistake I see is developers grouping elements together by technical type rather than functional purpose. They create a “technical silo” where every database model lives in one file, every Pydantic schema in another, and every helper function in a giant utils.py.
The “Technical Grouping” Trap (Bad):

In this structure, a “small” change to a user model in the massive models.py can cause a "domino effect," breaking the order application because they are tightly coupled to the same file.
Another common disaster is the “God Object” service. This is a single service file that tries to do a little bit of everything — fetching from a DB, parsing an external API, and handling business logic for three different components.
Core Principle 1: High Cohesion
Cohesion describes how closely the pieces within a single module relate to each other. A highly cohesive module has one well-defined responsibility. In FastAPI, you should use the APIRouter class to group related endpoints into logical sub-modules.
- Group Models with Endpoints: Instead of a global
models.py, your Pydantic and database models should live within the component that uses them. - The Power of Pydantic: Pydantic is a “wonderful library” that is often underutilised. It isn’t just for validation; it’s designed to extract and format data automatically.
- Avoid External Parsers: Many coders waste time writing external utility parsers to format data. This is a mistake. FastAPI uses Pydantic to convert input/output data automatically (like converting datetime objects or DB models to JSON). Keep this logic inside your Pydantic schemas to maintain high cohesion.
Core Principle 2: Low Coupling
Coupling is a measure of how much one component relies on another. In a loosely coupled design, modules can function independently and talk through stable, minimal APIs.
- Design for Independence: You should be able to develop, test, and deploy a module in isolation.
- Minimize Shared State: Avoid shared database schemas or global variables that tie modules together. If App A and App B both write to the same table in a giant shared
models.py, you've created a nightmare for yourself. - Dependency Injection: Leverage FastAPI’s Dependency Injection system. It makes it “VERY easy” to pass settings or add-ons between modules without creating rigid, hard-coded dependencies.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
The Recommended “Functional” Structure (Good)
To build a scalable application, you should follow the principles of functional cohesion. Group your code by the feature it serves, not the type of file it is.

By grouping the database logic and parsing within the component, you ensure that the development of individual services can be highly parallelised.
Conclusion: Designing for the Future
The freedom of FastAPI is a responsibility. Don’t fall into the trap of technical grouping just because it’s “fast” at the start. A system with well-decoupled, cohesive components is easier to understand, less prone to bugs, and far more adaptable to change.
Next time you start a project, don’t just dump everything into a utils.py or a massive schemas.py. Design intentionally, use Pydantic for its intended purpose of extraction and formatting, and keep your components independent.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Written by Marc Nealer Python data-driven app developer and freelancer. Reach out at: marc.nealer@gmail.com
