- Choose between file-type and feature-based layouts based on project size, architecture and team needs, keeping consistency as the top priority.
- Leverage FastAPI’s type hints, Pydantic models and dependency injection to keep routing thin, logic modular and validation both powerful and reusable.
- Structure security, testing, async I/O and configuration as first-class concerns so that scaling to production and multiple teams does not require a full rewrite.
- Adopt clear naming, migration and tooling conventions to keep a growing FastAPI monolith or microservice fleet maintainable over time.
Designing a clean, scalable structure for a FastAPI project is one of those decisions that seems minor at the beginning but completely changes your life six months later, when the codebase has grown, the team has expanded and you are trying to trace a bug across multiple modules. A good layout accelerates onboarding, reduces regressions and makes refactors far less painful.
FastAPI gives you enormous flexibility, but that also means you must actively choose and enforce conventions. In this guide we will pull together the most useful ideas from real‑world, production FastAPI projects and well‑known best‑practice repositories, compare the main project layouts, and see how they interact with core FastAPI concepts such as dependency injection, validation, async I/O, testing and deployment.
Why project structure matters so much in FastAPI
A solid structure is the backbone that keeps a FastAPI application understandable as it grows in size and complexity. Without it, even a framework with great ergonomics quickly turns into a pile of ad‑hoc modules, circular imports and duplicated logic.
From an engineering perspective, structure directly influences scalability: when modules are decoupled and responsibilities are clear, you can split services, introduce queues or scale different parts of the system independently without rewriting everything.
Maintainability is another big win. When every route, schema and database model has a predictable home, developers waste less time searching files and more time solving actual business problems. It also becomes easier to perform code reviews because everyone shares the same mental model.
On team projects, a consistent layout massively improves collaboration. New hires can infer where to put new features, QA can discover the right entry points to test, and DevOps can understand which pieces are critical to boot the app (for example, where the ASGI app object lives and how the database is wired).

Core principles for structuring FastAPI projects
Before choosing a folder layout, it helps to agree on a few principles that should guide any architecture decision. These ideas show up repeatedly in successful FastAPI codebases.
Separation of concerns is the first pillar: keep routing, persistence, business rules and integration code in different places. Endpoints should orchestrate use cases, not embed SQL queries, JSON manipulation and external service calls all in one long function.
Modularity is the second pillar. Instead of one giant monolithic package, aim for smaller, focused modules or subpackages that encapsulate related behavior. This makes it much easier to reuse pieces, test them in isolation, and eventually split them into microservices if needed.
Dependency injection is the glue that connects those modules without tight coupling. FastAPI’s dependency system allows you to declare what a route or service needs (database session, configuration, authenticated user, etc.) and let the framework provide it, which is ideal for testability and reuse.
Testability itself must be treated as a first‑class requirement. If the structure makes it hard to spin up the app in memory, override dependencies and hit endpoints with a test client, you will either skip tests or fight your own architecture. Good structure keeps side effects local and paths importable from tests.
Two dominant FastAPI project layouts: by file type vs by feature
In the FastAPI ecosystem you will usually find two clear families of layouts: projects organized by the type of file (routers, models, schemas, CRUD) and projects organized by domain or feature (auth, users, posts, payments, etc.). Each one shines in a different context.
File‑type‑based structure (routers, schemas, models, CRUD)
The file‑type approach mirrors how many official examples and tutorials introduce FastAPI. You group code according to its technical role: routing layer, pydantic schemas, database models, CRUD operations, utilities and so on.
A minimal but realistic layout might look like this (shortened for clarity):
Example layout: .├── app
│ ├── __init__.py
│ ├── main.py # FastAPI app initialization
│ ├── dependencies.py # shared dependencies
│ ├── routers
│ │ ├── __init__.py
│ │ ├── items.py # endpoints for items
│ │ └── users.py # endpoints for users
│ ├── crud
│ │ ├── item.py # item CRUD
│ │ └── user.py # user CRUD
│ ├── schemas
│ │ ├── item.py # pydantic models for items
│ │ └── user.py # pydantic models for users
│ ├── models
│ │ ├── item.py # ORM models for items
│ │ └── user.py # ORM models for users
│ ├── external_services
│ │ ├── email.py # email provider client
│ │ └── notification.py # push / notification client
│ └── utils
│ ├── authentication.py
│ └── validation.py
├── tests
│ ├── test_main.py
│ ├── test_items.py
│ └── test_users.py
├── requirements.txt
└── README.md
In this style, each top‑level directory under app/ has a single responsibility. For example, routers/ describes HTTP entry points, schemas/ declares input/output shapes and models/ represent database tables.
This layout tends to work extremely well for small to medium services or microservices. The domain is usually narrow enough that splitting by technical role does not produce friction. Most endpoints use the same limited set of models, and there are only a few teams touching the code at the same time.
Key benefits of the file‑type structure include a very low cognitive load for beginners and a directory tree that closely follows FastAPI’s documentation. For someone learning the framework, seeing a dedicated routers/ folder and a schemas/ folder often feels more intuitive than jumping straight into domain‑driven packaging.
Feature‑or module‑oriented structure under src/
As projects grow into monoliths with many domains, the file‑type layout starts to creak. You get giant directories such as routers/ with dozens of files, complex cross‑module imports, and business logic scattered across unrelated packages.
An alternative that scales better is the feature‑based, or domain‑driven, structure. Here you place all the code for a particular domain in one subpackage: routes, schemas, models, services, configuration and module‑specific exceptions.
A representative layout inspired by popular best‑practice repositories looks like this:
Representative file tree: fastapi-project
├── alembic/
├── src
│ ├── auth
│ │ ├── router.py # auth endpoints
│ │ ├── schemas.py # pydantic models
│ │ ├── models.py # DB models
│ │ ├── dependencies.py # auth-specific dependencies
│ │ ├── config.py # local configs
│ │ ├── constants.py # auth error codes / constants
│ │ ├── exceptions.py # auth-specific exceptions
│ │ ├── service.py # business logic
│ │ └── utils.py # helpers
│ ├── aws
│ │ ├── client.py
│ │ ├── schemas.py
│ │ ├── config.py
│ │ ├── constants.py
│ │ ├── exceptions.py
│ │ └── utils.py
│ ├── posts
│ │ ├── router.py
│ │ ├── schemas.py
│ │ ├── models.py
│ │ ├── dependencies.py
│ │ ├── constants.py
│ │ ├── exceptions.py
│ │ ├── service.py
│ │ └── utils.py
│ ├── config.py # global configs
│ ├── models.py # shared DB models
│ ├── exceptions.py # shared exceptions
│ ├── pagination.py # reusable pagination logic
│ ├── database.py # DB connection & session management
│ └── main.py # FastAPI app factory / entry point
├── tests
│ ├── auth
│ ├── aws
│ └── posts
├── templates
│ └── index.html
├── requirements
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
├── .env
├── logging.ini
└── alembic.ini
In this world, src/ is the top of the internal application tree. Every domain, like auth or posts, becomes almost a mini‑service: it carries its own router, schemas, models, constants, error types and business service layer.
The main advantage is locality of change: when you add a new feature to posts, you rarely have to touch any unrelated package. Tests can also live alongside their features (for example under tests/posts/), which encourages higher coverage.
This structure is particularly suited for monolithic applications with many domains and teams, where you want to support parallel work and reduce merge conflicts. It also plays nicely with domain‑driven design concepts like bounded contexts and aggregates.

Choosing the right layout for your FastAPI project
Picking between a file‑type and a feature‑based structure is not about right vs wrong, but about matching your architecture and growth expectations. A tiny internal tool with just a handful of endpoints will not benefit much from an elaborate domain layout.
For microservices and narrowly scoped APIs, the file‑type layout is usually simpler. Each service often focuses on a single responsibility (a billing API, a notification sender, a reporting microservice), and developers already know intuitively where to put new routes or schemas.
For bigger monoliths, splitting by domain nearly always wins in the long run. When you have modules for profiles, subscriptions, content, payments, analytics and more, putting every router under a single directory becomes chaotic. Grouping by feature keeps each slice of the system self‑contained.
Also consider your team structure. If one small team owns the whole codebase, consistency may be easier to maintain with a simple layout. If multiple teams share the monolith and each one owns a domain area, a feature‑based structure lets them move faster without stepping on each other’s toes.
Whatever you choose, consistency is more important than the exact shape of the tree. Changing the layout mid‑project is painful, so investing some thought early and writing down a short internal style guide will pay off later.
Understanding FastAPI itself: what you are structuring
To design a sensible structure you need a clear mental model of what FastAPI actually does for you. At its core, FastAPI is an ASGI web framework focused on building HTTP APIs with Python 3.7+ using type hints.
FastAPI leans heavily on Pydantic for data validation and serialization. It offers far more than simple “required vs optional” fields; you can express rich constraints and transformations directly on your models.
Because the OpenAPI schema is derived directly from your endpoints and models, FastAPI also generates interactive documentation. Out of the box you get Swagger UI at /docs and ReDoc at /redoc, which are invaluable when collaborating with frontend developers or third‑party integrators.
Under the hood, FastAPI runs on top of ASGI servers such as Uvicorn. This lets your app handle many concurrent connections efficiently, and enables features like long‑lived WebSocket connections without extra ceremony.
FastAPI is also explicit about requests and responses. Each endpoint is just a regular Python function (sync or async) decorated with @app.get, @app.post and friends, receiving path/query/body data and returning a response, typically a dict or Pydantic model.
Async vs sync: how performance intersects with structure
FastAPI is designed first and foremost as an async framework, but it supports both async and sync endpoints. Understanding how they behave internally will inform how you design services, choose clients and structure modules handling I/O.
When you declare an async def route, FastAPI runs it directly in the event loop. The framework assumes that any long‑running operations inside will be non-blocking awaitables, such as an async database driver or an HTTP client built on asyncio.
If you accidentally place blocking calls (for example time.sleep(), CPU‑heavy loops or slow libraries that do network I/O synchronously) inside those async routes, you will effectively freeze the event loop. No other requests will be processed until that operation finishes, which defeats the purpose of async.
Sync routes behave differently: FastAPI executes them in a thread pool. Blocking work in those routes only holds a worker thread, not the entire event loop, so the server can still accept new requests. This is how FastAPI remains flexible when you must rely on synchronous libraries.
The same principles apply to dependencies. Both sync and async dependencies are supported, but sync dependencies are also run in a thread pool. For tiny non‑I/O helpers it is often better to mark them async to avoid the overhead and limitations of threads when it is unnecessary.
CPU‑bound workloads are a separate story. Whether you run them sync or async inside the process, the GIL (Global Interpreter Lock) means only one thread is executing Python bytecode at a time. For tasks like heavy data processing, image transcoding or ML inference, consider offloading work to workers in separate processes or external queues.
Validating and shaping data with Pydantic
Pydantic is the engine behind FastAPI’s validation and serialization features. It offers far more than simple “required vs optional” fields; you can express rich constraints and transformations directly on your models.
Typical use cases include validating string length, numeric ranges, email formats or complex nested structures. Models can also rely on enums, regular expressions, custom validators and many other tools to sanitize inputs before they hit your business logic.
A powerful pattern is to define a custom base model for your project. By inheriting all schemas from a single base class, you can standardize cross‑cutting behavior such as datetime serialization format, utility methods that return only JSON‑safe values, or common configuration flags.
Pydantic is great for pure data validation, but it does not know anything about your database or external services. If your rules depend on a query (like checking that a user ID exists, or an email is unique), a common best practice is to move those validations into dependencies rather than into Pydantic model validators.
That way, you keep schemas declarative and reuse validations across multiple endpoints. A dependency can fetch an entity, enforce access rules and inject the result into the route, while FastAPI caches its result per request to avoid duplicate work when the same dependency is used multiple times.
Dependencies as a building block for clean architecture
FastAPI’s dependency injection system is not just syntax sugar for parameters; it is a core architectural tool. Proper use of dependencies lets you share logic, enforce invariants and keep routes very small and expressive.
Common examples include database session providers, configuration loaders, authentication helpers and pagination parsers. Rather than manually opening and closing sessions or repeating pagination parameters everywhere, you declare them once as dependencies and reuse them.
A subtle but important design tip is to break dependencies into small, composable units. Instead of one giant get_current_user_with_all_checks function, you might have separate dependencies for parsing the JWT, loading the user, verifying the account is active and checking that the user owns a given resource.
Because FastAPI caches dependency results within a single request, composing them is cheap. If three different dependencies reuse a lower‑level helper (for example, parsing JWT claims), that helper will only run once per request even if referenced multiple times.
When designing routes, path naming can either help or hinder dependency reuse. For instance, if several endpoints validate that a profile_id exists, using that same name consistently in path parameters makes it easier to plug in a single dependency that relies on profile_id, instead of inventing many variations such as creator_id that carry the same meaning.
Security, authentication and authorization in your structure
Security is one of the areas where a clear structure pays off quickly. Mixing authentication logic directly into random routes makes it hard to audit access rules and easy to accidentally expose data.
A common pattern in feature‑based layouts is to have an auth package with its own router, schemas, service layer and exceptions. That module will handle user registration, login flows, token issuing and verification, and may define dependencies like get_current_user that other modules import.
Within that auth package you can support multiple authentication mechanisms, like OAuth2 with password and bearer tokens, API keys for service‑to‑service calls, or JWT‑based tokens for stateless APIs. FastAPI’s fastapi.security utilities help you describe these flows in OpenAPI as well.
It is crucial to separate authentication (who the user is) from authorization (what they are allowed to do). Your structure should make it obvious where permission checks live: for instance, in a dedicated service or policy layer instead of scattered ad‑hoc if statements across every route.
Whenever you deal with passwords, follow established cryptographic practices. Hash secrets with a slow, salted algorithm such as bcrypt or argon2 via reputable libraries, avoid manual crypto and treat token storage, CSRF protection and transport security (HTTPS) as first‑class parts of the design.
Testing FastAPI applications effectively
Structure and testing reinforce each other: a clean layout naturally leads to more testable code. With FastAPI you can test at several levels, from pure unit tests of services to full integration tests hitting the HTTP layer.
Unit tests should focus on small, side‑effect‑free pieces: pure functions, Pydantic validators, business services that operate on in‑memory data. These tend to be very fast and are your first line of defense against regressions.
For exercising real HTTP endpoints, you can use the built‑in test client based on Starlette or modern async clients such as httpx. The idea is to import your app, override dependencies as needed (for example, injecting a test database session) and send requests without running an external server.
If you are working with async database drivers or other async integrations, it is worth setting up an async test client from the start. Mixing sync and async test styles later on often leads to confusing event‑loop issues that are harder to debug than simply standardizing on one approach.
Database usage in tests also interacts with your structure. By having a central database.py module that defines engine and session factories, it becomes easier to spin up test databases, wrap tests in transactions or use fixtures that truncate tables between runs.
From local development to production deployment
FastAPI applications are trivial to run locally but require a bit more planning in production. Your project structure should make it obvious how the app is created, where configuration comes from and how logging and health checks are wired.
For development, most teams use Uvicorn with auto‑reload, typically via a command like uvicorn app.main:app --reload or, in newer setups, fastapi dev. This provides fast feedback loops and is perfect while iterating.
In production you usually want a more robust setup: Uvicorn or Hypercorn workers managed by a process supervisor or WSGI/ASGI wrapper such as Gunicorn, often behind a reverse proxy (NGINX or a managed load balancer). The goal is to control worker counts, timeouts and graceful restarts, informed by modern DevOps practices.
Configuration should be driven by environment variables rather than hard‑coded values. Pydantic’s settings management or similar tools can help you declare typed settings classes and load them at startup, centralizing all environment‑specific knobs in a single place.
Before calling your app production‑ready, check a few essentials: structured logging, basic metrics, health endpoints for liveness and readiness probes, sensible body size limits, and a clear policy about exposing documentation only in non‑public environments if your API is not meant for general use.
Naming, database design and migrations
How you name things in your models and schema files is part of your project structure too. Inconsistent naming is one of the fastest ways to confuse developers working on a codebase they did not create.
A simple, effective convention is to use lower_case_snake names for tables and columns, prefer singular table names (for example post, post_like, user_playlist) and group related tables with a common prefix such as payment_ or post_.
For temporal fields, suffixes like _at for datetimes and _date for plain dates keep things clear. Being strict here prevents the “is this a timestamp or a date?” guessing game when reading schemas or raw queries.
Migrations deserve special care; they should be deterministic, reversible and descriptive — consider following database migrations practices. Many teams adopt a pattern for migration filenames like YYYY-MM-DD_slug.py, which makes it easier to track history and understand what changed without reading the full diff.
For complex reporting or nested responses, embrace your database rather than over‑processing in Python. Modern SQL engines can perform joins, aggregations and JSON building far faster than CPython, and returning pre‑shaped structures to FastAPI often simplifies your response models.
Tooling, formatting and team conventions
A well‑structured project is easier to keep clean when you enlist tooling to enforce style rules. Code formatters, linters and pre‑commit hooks help you focus on business logic instead of arguing about whitespace in code reviews.
Recently, tools like Ruff have become popular because they combine multiple roles in one. Instead of juggling separate utilities for formatting, import sorting and linting, you can run a single fast tool that enforces hundreds of rules across the codebase.
Running these tools via a simple script or pre‑commit hooks keeps the barrier low. Not every team needs an elaborate hook setup, but having at least a single command that standardizes the code layout is an easy win.
Finally, consider documenting your internal conventions in a short “engineering handbook” that references agile software development methods. Describe how modules should be named, when to create a new domain package, how to structure tests, and which security patterns are mandatory. This prevents knowledge from living only in senior developers’ heads.
Designing the structure of a FastAPI project is really about making future work predictable and boring: when every new endpoint, model or service has an obvious home, developers can move quickly without surprises, security is easier to audit and the application stands a much better chance of surviving real‑world growth without collapsing under its own weight.