FastAPI 项目结构和最佳实践指南

最后更新: 12/27/2025
作者: C 源跟踪
  • 根据项目规模、架构和团队需求,在文件类型布局和基于功能的布局之间进行选择,并将一致性作为首要任务。
  • 利用 FastAPI 的类型提示、Pydantic 模型和依赖注入,使路由精简、逻辑模块化,并使验证既强大又可重用。
  • 将结构安全、测试、异步 I/O 和配置作为首要考虑因素,以便扩展到生产环境和多个团队时无需完全重写。
  • 采用清晰的命名、迁移和工具约定,以保持不断增长的 FastAPI 单体或微服务集群的长期可维护性。

FastAPI 项目结构最佳实践

为 FastAPI 项目设计一个简洁、可扩展的架构,乍看之下似乎微不足道,但六个月后却会彻底改变你的工作方式。当代码库不断增长、团队不断扩张,而你又试图跨多个模块追踪一个 bug 时,良好的代码布局就显得尤为重要。它可以加快新员工入职速度,减少回归错误,并大大降低重构的难度。

FastAPI 提供了极大的灵活性,但也意味着您必须主动选择并执行约定。在本指南中,我们将汇集来自真实世界生产 FastAPI 项目和知名最佳实践库的最有用想法,比较主要项目布局,并了解它们如何与 FastAPI 的核心概念(如依赖注入、验证、异步 I/O、测试和部署)进行交互。

为什么项目结构在 FastAPI 中如此重要

随着 FastAPI 应用程序规模和复杂性的增长,一个稳固的结构是其保持易于理解的基石。如果没有它,即使是符合人体工程学的框架也会很快变成一堆临时模块、循环导入和重复逻辑。

从工程角度来看,结构直接影响可扩展性。当模块解耦且职责明确时,您可以拆分服务、引入队列或独立扩展系统的不同部分,而无需重写所有内容。

可维护性是另一大优势。当每个路由、模式和数据库模型都有其固定的位置时,开发人员就能减少查找文件的时间,从而将更多精力投入到解决实际业务问题上。此外,由于每个人都拥有相同的思维模型,代码审查也变得更加容易。

在团队项目中,统一的布局能够极大地提升协作效率。新员工可以推断新功能应该放在哪里,QA 可以发现正确的测试入口点,DevOps 可以了解哪些部分对于启动应用程序至关重要(例如,ASGI 应用程序对象位于何处以及数据库是如何连接的)。

FastAPI文件夹布局图

FastAPI 项目结构的核心原则

在选择文件夹布局之前,最好先就一些指导任何架构决策的原则达成一致。这些理念在成功的 FastAPI 代码库中反复出现。

关注点分离 首要原则是:将路由、持久化、业务规则和集成代码放在不同的位置。端点应该负责协调各种用例,而不是将 SQL 查询、JSON 操作和外部服务调用全部嵌入到一个冗长的函数中。

模块化是第二大支柱与其构建一个庞大的整体式软件包,不如构建更小、更专注的模块或子包,将相关行为封装起来。这样可以更轻松地重用各个组件,进行独立测试,并在需要时最终将其拆分为微服务。

依赖注入就像粘合剂,将那些没有紧密耦合的模块连接起来。FastAPI 的依赖系统允许您声明路由或服务需要什么(数据库会话、配置、已认证用户等),并让框架提供这些,这对于可测试性和重用性来说非常理想。

可测试性本身必须被视为首要要求。如果架构导致难以在内存中启动应用程序、覆盖依赖项以及使用测试客户端访问端点,那么你要么会跳过测试,要么会与自己的架构作斗争。良好的架构应确保副作用位于本地,并且路径可以从测试中导入。

FastAPI 项目布局主要有两种:按文件类型布局和按功能布局。

在 FastAPI 生态系统中,通常会发现两大类布局。项目按文件类型(路由、模型、模式、CRUD)组织,也按领域或功能(身份验证、用户、帖子、支付等)组织。每种组织方式在不同的场景下都能发挥优势。

基于文件类型的结构(路由器、模式、模型、CRUD)

文件类型方法与许多官方示例和教程介绍 FastAPI 的方式类似。您可以根据代码的技术作用对其进行分组:路由层、pydantic 模式、数据库模型、CRUD 操作、实用程序等等。

一个简洁而实用的布局可能如下所示 (为清晰起见已缩短)

示例布局: .├── 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

在这种风格中,每个顶级目录都位于 app/ 只有一个职责。 例如, routers/ 描述HTTP入口点 schemas/ 声明输入/输出形状和 models/ 表示数据库表。

这种布局对于中小型服务或微服务来说往往非常有效。该领域通常足够狭窄,因此按技术角色划分不会产生摩擦。大多数端点使用相同的有限模型集,而且同时只有少数几个团队在修改代码。

文件类型结构的主要优点之一是对初学者来说认知负荷非常低。 以及一个与 FastAPI 文档紧密相关的目录树。对于正在学习该框架的人来说,看到一个专门的目录树会很有帮助。 routers/ 文件夹和一个 schemas/ 相比直接采用领域驱动的打包方式,文件夹通常感觉更直观。

面向特性或模块的结构 src/

随着项目发展成拥有众多域的大型系统,文件类型布局开始显得不堪重负。你会看到诸如此类的大型目录 routers/ 包含数十个文件、复杂的跨模块导入,以及分散在不相关软件包中的业务逻辑。

另一种扩展性更好的替代方案是基于特征或领域驱动的结构。在这里,您可以将特定域的所有代码放在一个子包中:路由、模式、模型、服务、配置和模块特定的异常。

受流行的最佳实践代码库启发,一个典型的布局如下所示。:

文件树示例: 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

在这个世界上, src/ 是内部应用程序树的顶层。每个域名,例如 auth or posts它几乎变成了一个迷你服务:它拥有自己的路由器、模式、模型、常量、错误类型和业务服务层。

主要优势在于变化的局部性。当你添加新功能时 posts你几乎不需要修改任何无关的包。测试也可以与它们的功能共存(例如在……之下)。 tests/posts/),从而促进更高的覆盖率。

这种结构特别适合具有多个域和团队的单体应用程序。它适用于需要支持并行工作并减少合并冲突的场景。它还能很好地与领域驱动设计理念(例如限界上下文和聚合)相兼容。

FastAPI 域和模块

为您的 FastAPI 项目选择合适的布局

选择文件类型结构还是基于特性的结构并非孰优孰劣之分,而是要考虑架构和增长预期是否匹配。一个只有几个端点的小型内部工具,不会从复杂的域布局中受益匪浅。

对于微服务和范围较窄的 API,文件类型布局通常更简单。每个服务通常专注于单一职责(计费 API、通知发送器、报告微服务),开发人员已经凭直觉知道在哪里放置新的路由或模式。

对于规模较大的单体应用来说,从长远来看,按领域拆分几乎总是更优的选择。当您拥有用于用户画像、订阅、内容、支付、分析等功能的模块时,将所有路由都放在同一个目录下会变得混乱不堪。按功能分组可以确保系统的每个部分都独立运行。

还要考虑你的团队结构如果整个代码库由一个小型团队负责,那么采用简单的架构可能更容易维护代码一致性。如果多个团队共享单体应用,并且每个团队负责一个领域,那么基于特性的结构可以让他们在不互相干扰的情况下更快地推进项目。

无论你选择哪种方式,保持一致性比树的具体形状更重要。在项目进行过程中更改布局会很麻烦,因此,尽早投入一些思考并编写一份简短的内部风格指南,以后会有回报的。

了解 FastAPI 本身:您正在构建什么

要设计一个合理的架构,你需要对 FastAPI 的实际功能有一个清晰的认识。FastAPI 的核心是一个 ASGI Web 框架,专注于使用类型提示和 Python 3.7+ 构建 HTTP API。

FastAPI 大量依赖 Pydantic 进行数据验证和序列化。它提供的功能远不止简单的“必填字段与选填字段”;您可以直接在模型上表达丰富的约束和转换。

由于 OpenAPI 模式直接源自您的端点和模型,因此 FastAPI 还会生成交互式文档。开箱即用,即可获得 Swagger UI。 /docs 以及 ReDoc /redoc这些工具在与前端开发人员或第三方集成商合作时非常宝贵。

FastAPI 底层运行在 ASGI 服务器(例如 Uvicorn)之上。这样一来,你的应用程序就能高效地处理多个并发连接,并且无需额外配置即可实现诸如长时间的 WebSocket 连接等功能。

FastAPI 对请求和响应也有明确的定义。每个端点都只是一个普通的 Python 函数(同步或异步),并用以下方式装饰: @app.get, @app.post 以及其他相关程序,接收路径/查询/正文数据并返回响应,通常是一个字典或 Pydantic 模型。

异步与同步:性能如何与结构相互作用

FastAPI 首先被设计成一个异步框架,但它也支持异步和同步端点。了解它们的内部运行方式将有助于您设计服务、选择客户端以及构建处理 I/O 的模块。

当你声明一个 async def 路由方面,FastAPI 直接在事件循环中运行它该框架假定内部任何长时间运行的操作都将是非阻塞的可等待对象,例如异步数据库驱动程序或基于 HTTP 客户端构建的异步数据库驱动程序。 asyncio.

如果您不小心拨打了阻塞电话(例如 time.sleep()如果在这些异步路由内部存在占用大量 CPU 资源的循环或执行同步网络 I/O 的慢速库,则实际上会冻结事件循环。在该操作完成之前,不会处理其他请求,这违背了异步的初衷。

同步路由的行为有所不同:FastAPI 在线程池中执行它们。在这些路由中阻塞操作只会阻塞一个工作线程,而不会阻塞整个事件循环,因此服务器仍然可以接受新的请求。这就是 FastAPI 在必须依赖同步库时保持灵活性的原因。

同样的原则也适用于依赖关系。同步和异步依赖项均受支持,但同步依赖项也会在线程池中运行。对于小型非 I/O 辅助函数,通常最好将其标记为异步,以避免不必要的线程开销和限制。

CPU密集型工作负载是另一回事。无论在进程内同步还是异步运行,全局解释器锁 (GIL) 都意味着同一时间只有一个线程在执行 Python 字节码。对于繁重的数据处理、图像转码或机器学习推理等任务,建议将工作卸载到独立进程或外部队列中的工作线程。

使用 Pydantic 验证和处理数据

Pydantic 是 FastAPI 验证和序列化功能背后的引擎。它提供的功能远不止简单的“必填字段与选填字段”;您可以直接在模型上表达丰富的约束和转换。

典型应用场景包括验证字符串长度、数值范围、电子邮件格式或复杂的嵌套结构。模型还可以依靠枚举、正则表达式、自定义验证器和许多其他工具来清理输入,然后再将其传递给业务逻辑。

一种有效的模式是为您的项目定义自定义基础模型。. 通过从单个基类继承所有模式,您可以标准化横切行为,例如日期时间序列化格式、仅返回 JSON 安全值的实用方法或通用配置标志。

Pydantic 非常适合纯粹的数据验证,但它对您的数据库或外部服务一无所知。如果您的规则依赖于查询(例如检查用户 ID 是否存在,或者电子邮件是否唯一),则常见的最佳实践是将这些验证移至依赖项中,而不是移至 Pydantic 模型验证器中。

这样,您就可以保持模式的声明性,并在多个端点之间重用验证。依赖项可以获取实体、强制执行访问规则并将结果注入路由,而 FastAPI 会缓存每个请求的结果,以避免多次使用同一依赖项时进行重复工作。

依赖关系是构建整洁架构的基石

FastAPI 的依赖注入系统不仅仅是参数的语法糖;它是一个核心架构工具。正确使用依赖项可以让你共享逻辑、强制执行不变式,并保持路由非常小巧且富有表现力。

常见的例子包括数据库会话提供程序、配置加载器、身份验证辅助程序和分页解析器。与其手动打开和关闭会话或在各处重复分页参数,不如将它们声明为依赖项并重复使用。

一个微妙但重要的设计技巧是将依赖关系拆分成小的、可组合的单元。而不是一个巨大的 get_current_user_with_all_checks 函数可能需要单独的依赖项来解析 JWT、加载用户、验证帐户是否处于活动状态以及检查用户是否拥有给定资源。

由于 FastAPI 会缓存单个请求中的依赖关系结果,因此组合这些结果的成本很低。如果三个不同的依赖项重用一个底层辅助函数(例如,解析 JWT 声明),即使多次引用该辅助函数,每个请求也只会运行一次。

在设计路由时,路径命名既可以促进依赖项的重用,也可以阻碍依赖项的重用。例如,如果多个端点验证了某个 profile_id 如果存在,在路径参数中始终使用相同的名称,则更容易插入依赖于该名称的单个依赖项。 profile_id而不是发明许多变体,例如 creator_id 意思相同。

结构中的安全、身份验证和授权

安全领域中,清晰的架构能够迅速带来回报。将身份验证逻辑直接混入随机路由中,使得访问规则难以审核,并且容易意外泄露数据。

基于特征的布局中常见的模式是: auth 包含自身路由器、模式、服务层和异常处理的软件包该模块将处理用户注册、登录流程、令牌颁发和验证,并可能定义依赖项,例如 get_current_user 其他模块导入的内容。

在该身份验证包中,您可以支持多种身份验证机制。例如,使用密码和持有者令牌的 OAuth2、用于服务间调用的 API 密钥,或用于无状态 API 的基于 JWT 的令牌。FastAPI 的 fastapi.security 实用程序还可以帮助您在 OpenAPI 中描述这些流程。

将身份验证(用户身份)与授权(用户被允许执行的操作)分开至关重要。您的架构应该清晰地表明权限检查的位置:例如,在专门的服务层或策略层中,而不是分散的临时代码层。 if 每条路由上的语句。

无论何时处理密码,都应遵循既定的加密规范。使用 bcrypt 或 argon2 等速度较慢的加盐算法通过信誉良好的库对密钥进行哈希处理,避免手动加密,并将令牌存储、CSRF 保护和传输安全 (HTTPS) 作为设计中的首要部分。

有效测试 FastAPI 应用程序

结构和测试相辅相成:清晰的布局自然会带来更易于测试的代码。使用 FastAPI,您可以进行多层次的测试,从纯粹的服务单元测试到覆盖 HTTP 层的完整集成测试。

单元测试应侧重于小的、无副作用的部分。纯函数、Pydantic 验证器以及操作内存数据的业务服务。这些方法通常速度非常快,是抵御回归的第一道防线。

要测试真实的 HTTP 端点,您可以使用基于 Starlette 的内置测试客户端,或者使用 httpx 等现代异步客户端。其思路是导入你的应用程序,根据需要覆盖依赖项(例如,注入测试数据库会话),并在不运行外部服务器的情况下发送请求。

如果您正在使用异步数据库驱动程序或其他异步集成,那么从一开始就设置一个异步测试客户端是值得的。后期混合使用同步和异步测试风格往往会导致令人困惑的事件循环问题,这些问题比简单地采用一种标准化的方法更难调试。

测试中的数据库使用也会与您的结构交互。通过设立一个中央机构 database.py 定义引擎和会话工厂的模块,使得启动测试数据库、将测试包装在事务中或使用在运行之间截断表的 fixtures 变得更加容易。

从本地开发到生产部署

FastAPI 应用在本地运行非常简单,但在生产环境中则需要更多规划。你的项目结构应该清晰地展现应用程序的创建方式、配置来源以及日志记录和健康检查的实现方式。

在开发过程中,大多数团队使用带有自动重载功能的 Uvicorn。通常通过类似这样的命令 uvicorn app.main:app --reload 或者,在新式配置中, fastapi dev这提供了快速的反馈循环,非常适合迭代过程。

在生产环境中,通常需要更强大的配置。Uvicorn 或 Hypercorn 工作进程由进程管理器或 WSGI/ASGI 封装器(例如 Gunicorn)管理,通常位于反向代理(NGINX 或托管负载均衡器)之后。其目标是根据以下信息控制工作进程数量、超时时间和优雅重启: 现代DevOps实践.

配置应由环境变量驱动,而不是由硬编码值驱动。Pydantic 的设置管理或类似工具可以帮助您声明类型化的设置类并在启动时加载它们,将所有特定于环境的参数集中在一个地方。

在宣布您的应用已准备好投入生产之前,请检查以下几个基本要素::结构化日志记录、基本指标、用于检测存活状态和就绪状态的健康端点、合理的主体大小限制,以及明确的策略,即如果您的 API 不打算用于一般用途,则仅在非公共环境中公开文档。

命名、数据库设计和迁移

模型和模式文件中的命名方式也是项目结构的一部分。命名不一致是让开发人员在自己没有创建的代码库上工作时感到困惑的最快方法之一。

一个简单有效的惯例是使用 lower_case_snake 表和列的名称建议使用单数形式的表名(例如 post, post_like, user_playlist)并将具有共同前缀的相关表分组,例如 payment_ or post_.

对于时间字段,后缀如 _at 对于日期时间和 _date 对于普通的日期,请保持清晰明了。严格执行此规则可以避免在读取模式或原始查询时出现“这是时间戳还是日期?”的猜测游戏。

移民问题需要特别关注。它们应该是确定性的、可逆的和描述性的——请考虑遵循以下原则 数据库迁移实践许多团队都采用类似这样的迁移文件名模式 YYYY-MM-DD_slug.py这样一来,无需阅读完整的差异即可轻松跟踪历史记录并了解发生了哪些变化。

对于复杂的报表或嵌套响应,应该充分利用数据库,而不是在 Python 中过度处理。现代 SQL 引擎执行连接、聚合和 JSON 构建的速度远快于 CPython,而且将预先构建的结构返回给 FastAPI 通常可以简化响应模型。

工具、格式和团队惯例

如果使用工具来强制执行样式规则,结构良好的项目更容易保持整洁。代码格式化工具、代码检查工具和预提交钩子可以帮助你专注于业务逻辑,而不是在代码审查中争论空格问题。

最近,像 Ruff 这样的工具变得流行起来,因为它们将多种功能集成在一个工具中。与其使用多个单独的工具进行格式化、导入排序和代码检查,不如运行一个快速的工具,该工具可以在整个代码库中强制执行数百条规则。

通过简单的脚本或预提交钩子运行这些工具可以降低门槛。并非每个团队都需要复杂的钩子设置,但至少有一个命令可以规范代码布局,这无疑是一件好事。

最后,不妨考虑将内部规范记录在一份简短的“工程手册”中。 那个引用 敏捷软件开发方法描述模块的命名方式、何时创建新的域包、如何构建测试以及哪些安全模式是必需的。这可以防止知识仅仅存在于资深开发人员的头脑中。

FastAPI 项目的结构设计,实际上就是为了让未来的工作变得可预测且枯燥乏味。当每个新的端点、模型或服务都有明确的归属地时,开发人员可以快速行动而不会遇到意外,安全性更容易进行审计,应用程序也更有可能在现实世界的增长中生存下来,而不会因自身重量而崩溃。

相关文章:
已解决:快速 api 模板语法
相关文章: