zhanymkanov / fastapi-best-practices
FastAPI Best Practices and Conventions we used at our startup
AI Architecture Analysis
This repository is indexed by RepoMind. By analyzing zhanymkanov/fastapi-best-practices in our AI interface, you can instantly generate complete architecture diagrams, visualize control flows, and perform automated security audits across the entire codebase.
Our Agentic Context Augmented Generation (Agentic CAG) engine loads full source files into context on-demand, avoiding the fragmentation of traditional RAG systems. Ask questions about the architecture, dependencies, or specific features to see it in action.
Repository Overview (README excerpt)
Crawler viewFastAPI Best Practices Opinionated list of best practices and conventions we use at our startups. After several years of building production systems, we've made both good and bad decisions that significantly impacted our developer experience. Here are some lessons worth sharing. *简体中文* Contents • Project Structure • Async Routes • I/O Intensive Tasks • CPU Intensive Tasks • Pydantic • Excessively use Pydantic • Custom Base Model • Decouple Pydantic BaseSettings • Dependencies • Beyond Dependency Injection • Chain Dependencies • Decouple \& Reuse dependencies. Dependency calls are cached • Prefer dependencies • Miscellaneous • Follow the REST • FastAPI response serialization • If you must use sync SDK, then run it in a thread pool. • ValueErrors might become Pydantic ValidationError • Docs • Set DB keys naming conventions • Migrations. Alembic • Set DB naming conventions • SQL-first. Pydantic-second • Set tests client async from day 0 • Use ruff • Bonus Section Project Structure There are many ways to structure a project, but the best structure is one that is consistent, straightforward, and free of surprises. Many example projects and tutorials organize projects by file type (e.g., crud, routers, models), which works well for microservices or smaller projects. However, this approach didn't scale well for our monolith with many domains and modules. The structure I found more scalable and evolvable is inspired by Netflix's Dispatch, with some minor modifications. • Store all domain directories inside folder • - highest level of an app, contains common models, configs, and constants, etc. • - root of the project, which inits the FastAPI app • Each package has its own router, schemas, models, etc. • - is a core of each module with all the endpoints • - for pydantic models • - for db models • - module specific business logic • - router dependencies • - module specific constants and error codes • - e.g. env vars • - non-business logic functions, e.g. response normalization, data enrichment, etc. • - module specific exceptions, e.g. , • When package requires services or dependencies or constants from other packages - import them with an explicit module name Async Routes FastAPI is an async-first framework—it's designed to work with async I/O operations, which is why it's so fast. However, FastAPI doesn't restrict you to only routes; you can use routes too. This might confuse beginners into thinking they're the same, but they're not. I/O Intensive Tasks Under the hood, FastAPI can effectively handle both async and sync I/O operations: • FastAPI runs routes in a threadpool, so blocking I/O operations won't stop the event loop from executing other tasks. • If the route is defined as , it's called via and FastAPI trusts you to only perform non-blocking I/O operations. The caveat is that if you violate that trust and execute blocking operations within async routes, the event loop won't be able to run other tasks until the blocking operation completes. **What happens when we call:** • 1. FastAPI server receives a request and starts handling it • Server's event loop and all queued tasks wait until finishes • Since the route is , the server doesn't offload it to a threadpool—it blocks the entire event loop • Server won't accept any new requests while waiting • Server returns the response • Only after responding does the server resume accepting new requests • 1. FastAPI server receives a request and starts handling it • FastAPI sends the entire route to the threadpool, where a worker thread runs the function • While executes, the event loop continues processing other tasks (e.g., accepting new requests, calling the database) • The worker thread waits for to finish, independently of the main thread • The sync operation blocks only the worker thread, not the main event loop • When finishes, the server returns a response to the client • 1. FastAPI server receives a request and starts handling it • FastAPI awaits • Event loop continues processing other tasks from the queue (e.g., accepting new requests, calling the database) • When completes, the server finishes executing the route and returns a response to the client > [!WARNING] > Notes on the thread pool: > - Threads require more resources than coroutines, so they are not as cheap as async I/O operations. > - Thread pool has a limited number of threads, i.e. you might run out of threads and your app will become slow. Read more (external link) CPU Intensive Tasks The second caveat is that non-blocking awaitables and threadpool offloading are only beneficial for I/O intensive tasks (e.g., file operations, database calls, external API requests). • Awaiting CPU-intensive tasks (e.g., heavy calculations, data processing, video transcoding) provides no benefit since the CPU must actively work to complete them. In contrast, I/O operations are external—the server just waits for a response and can handle other tasks in the meantime. • Running CPU-intensive tasks in other threads is also ineffective due to the GIL. In short, the GIL allows only one thread to execute Python bytecode at a time, making threads ineffective for CPU-bound work. • To optimize CPU-intensive tasks, you should offload them to worker processes (e.g., using or a task queue like Celery). **Related StackOverflow questions of confused users** • https://stackoverflow.com/questions/62976648/architecture-flask-vs-fastapi/70309597#70309597 • Here you can also check my answer • https://stackoverflow.com/questions/65342833/fastapi-uploadfile-is-slow-compared-to-flask • https://stackoverflow.com/questions/71516140/fastapi-runs-api-calls-in-serial-instead-of-parallel-fashion Pydantic Excessively use Pydantic Pydantic has a rich set of features to validate and transform data. In addition to standard features like required and optional fields with default values, Pydantic has built-in data processing tools like regex validation, enums, string manipulation, email validation, and more. Cust…