The Ultimate Enterprise FastAPI Project Structure (2025)
A scalable FastAPI folder structure you can maintain: clear boundaries, zero router spaghetti, and guardrails for AI-written code.
The Ultimate Enterprise FastAPI Project Structure (2025)
If you want FastAPI to scale, the folder structure isn’t bikeshedding — it’s your future incident report in slow motion.
If you want the "why" behind every folder, copy‑paste templates, and the rules that keep AI-generated code from freelancing inside your routers, you're in the right place.
Video coming soon
- Use a feature-first layout under
domain/. - Keep routers thin: validate input, call a service, return a response.
- Enforce import boundaries in CI so the structure stays intact over time.
Want the ready-to-copy rules/configs? Grab the Cursor/Windsurf rules and templates here: /resources.
Table of Contents
Use this table of contents to jump directly to the section you need without reading the whole article.
- What is the best folder structure for FastAPI?
- The anti-patterns that quietly kill FastAPI projects
- The architecture: a mental model you can enforce
- The tooling: how to enforce the structure automatically
- Quick start: how to apply this structure to an existing messy repo
What is the best folder structure for FastAPI?
The best FastAPI structure separates domain, routers, and dependencies, keeps business logic out of route handlers, and uses dependency injection to avoid circular imports.
Here’s a pragmatic enterprise layout that works for monorepos, services, and long-lived products:
If your first reaction is “where did crud.py go?”, that’s the point: you’re forcing clear boundaries instead of letting CRUD helpers become a dumping ground.
The anti-patterns that quietly kill FastAPI projects
A common FastAPI failure mode is routers accumulating business logic and data access, which makes code harder to test, increases coupling, and encourages “just this one exception” drift.
Most FastAPI projects don’t fail because the code is “bad.” They fail because the architecture makes it impossible to stay good under pressure.
Why your models.py file is a ticking time bomb
If models.py becomes “the place where everything lives,” it turns into:
- A dependency magnet (everything imports it)
- A circular import factory (because it imports everything back)
- A schema drift generator (because Pydantic models start representing both transport and domain)
Fix: split by feature under domain/ and keep Pydantic schemas in schemas.py per feature.
Stop putting DB calls in your routers
Router handlers should coordinate:
- validate input
- call a service
- return a response
When routers do DB work, you get:
- untestable logic (you can’t unit test without a DB)
- inconsistent security checks
- “just this one exception” copy-paste everywhere
Fix: put business logic in domain/<feature>/service.py and data access in domain/<feature>/repo.py.
The architecture: a mental model you can enforce
A good FastAPI structure forces good decisions by making the “right” dependency direction the easiest path, so features scale without turning routers into a dumping ground.
The point of structure isn’t aesthetics — it’s forcing good decisions.
Domain-Driven Design (Lite): group by feature, not file type
Feature grouping is the only pattern that survives:
- multiple teams
- multiple databases
- “we added billing”
- “we renamed organizations to workspaces”
A folder like domain/users/ is a boundary. A folder like services/ is a confession.
The services layer: where the actual logic lives
The service layer is the only place where:
- invariants belong (e.g., “cannot delete the last admin”)
- workflows belong (e.g., “create user → send email → write audit log”)
- integration is coordinated
Your routers should read like orchestration, not like a novella.
The contracts layer: where you stop lying to yourself
“Contracts” is where you define stable outputs:
- response shapes
- error codes
- public schemas used across routers
This is also where AI-written code tends to drift first — it’ll happily introduce a new response shape on Tuesday because it “felt right.”
If you want deterministic systems, contracts must be boring.
The tooling: how to enforce the structure automatically
Enforce FastAPI architecture with import boundaries: routers can’t touch DB/session, services can’t import FastAPI, and violations should fail CI automatically.
You can enforce architecture manually in PR review… until you can’t.
Here’s the enforcement model:
- routers must not import DB/session
- services must not import FastAPI
- domain must not import routers
The pitch
You can review PRs manually, or you can use Ranex to reject any commit that imports db into routers.
That’s the Trojan Horse: you came here for a folder tree. You leave with a workflow that prevents the tree from turning into a jungle.
Quick start: how to apply this structure to an existing messy repo
Refactor by moving logic out of routers first, then isolating data access, then locking the rules with tooling so the structure stays fixed.
- Create
domain/<feature>/service.pyand move logic out of routers. - Create
domain/<feature>/repo.pyand move DB access behind an interface. - Create
dependencies/and put FastAPI deps there (session, auth, current_user). - Add “contract” response types and stop returning raw ORM objects.
- Enforce imports with tooling so it stays fixed.
About the Author

Anthony Garces
AI Infrastructure Engineer specializing in LLM governance and deployment
Related Articles
Deterministic Code Analysis: How to Stop AI Hallucinations in Real Codebases
Deterministic code intelligence uses symbol graphs so answers are reproducible. Why RAG drifts for code, and what works instead.
Why AI Agents Are Just Fancy While Loops (And Why That's Dangerous)
Most AI agent frameworks ship retry loops disguised as autonomy. Why probabilistic logic without state constraints will burn you.