Strapi 无头 CMS:从 README 到源码,看懂请求流水线与快速上手路径
本文以 Strapi 主仓库的 README 为骨架,系统讲解这个开源无头(headless)CMS 的核心定位、请求处理流水线(Routes → Middlewares → Controllers → Services)、快速安装与 Docker 部署方式,并结合仓库源码给出每一层的实现证据。读完后,你既能掌握从零创建 Strapi 项目的操作路径,也能理解自动生成的 REST API 背后的路由契约、内置中间件清单与默认行为,从而在二次开发(自定义路由、中间件、服务)时有的放矢。
一、Strapi 是什么:README 中的产品定位与能力清单
README 开篇给出的定义是:Strapi 是一个开源、可自托管的无头 CMS,让开发者快速构建内容 API,同时为内容编辑者提供友好的管理界面。你定义内容模型,Strapi 就生成一套完整的 API,可被任意前端、移动端或 IoT 设备消费(见 README.md)。
README 列举的核心能力,在当前仓库中都有对应的代码实体,逐条对照如下:
| README 宣称的能力 | 仓库中的对应实现 |
|---|---|
| 可视化内容建模,无需写代码(Content-Type Builder) | packages/core/content-type-builder |
| 为每个内容类型自动生成 REST & GraphQL API | 路由自动生成逻辑在 packages/core/core/src/core-api/routes/index.ts;GraphQL 插件在 packages/plugins/graphql |
| 开箱即用的细粒度角色与权限 | packages/plugins/users-permissions |
| 内置媒体库(Media Library) | packages/plugins/upload |
| 国际化(i18n)与草稿/发布(Draft & Publish) | packages/plugins/i18n;草稿发布的数据迁移逻辑见 packages/core/core/src/migrations/draft-publish.ts |
| 一流 TypeScript 支持,数据库可选 SQLite / PostgreSQL / MySQL / MariaDB | 数据库层在 packages/core/database;TypeScript 基础设施见 packages/core/types |
| 可扩展的插件系统与可定制后台 | 插件加载器在 packages/core/core/src/loaders,官方插件示例在 examples/plugins/todo-example |
需要说明的是:README 中的“Strapi AI”“Strapi Cloud 托管”“LaunchPad 演示模板”属于外部商业化/社区生态内容,不在本仓库源码范围内,本文不展开。
二、请求处理流水线:Routes → Middlewares → Controllers → Services
README 中最具技术价值的部分是一张请求流向说明:每一个进入的请求都会流经分层后端架构——Routes → Middlewares → Controllers → Services。下面逐层给出源码级验证。
2.1 路由层:API 路由是如何“自动生成”的
Strapi 的“零代码生成 API”并非魔术。从 packages/core/core/src/core-api/routes/index.ts 可以看到,路由工厂 createRoutes 会先判断内容类型是单例(single type)还是集合(collection type),然后返回一套带方法、路径、处理器和请求/响应契约(基于 Zod schema 的 request/response 字段)的路由定义。
集合类型(Collection Type)自动生成的 5 个端点(路径基于 info.pluralName 复数名):
| 操作 | 方法 | 路径 | handler |
|---|---|---|---|
列表查询 find |
GET | /:pluralName |
api::xxx.findOne 对应的 find |
单条查询 findOne |
GET | /:pluralName/:id |
xxx.findOne |
创建 create |
POST | /:pluralName |
xxx.create |
更新 update |
PUT | /:pluralName/:id |
xxx.update |
删除 delete |
DELETE | /:pluralName/:id |
xxx.delete |
单例类型(Single Type) 只生成 find(GET)、update(PUT)、delete(DELETE)三个端点,路径直接使用 info.singularName 单数名——因为不存在“多条记录”与“按 ID 定位”的概念。
查询参数同样由源码决定,而非口头约定:
- 集合列表固定支持
fields、filters、_q、pagination、sort、populate; - 条件性参数由
getConditionalQueryParams(routes/index.ts#L159-L173)按内容类型能力注入:开启 i18n 的本地化类型会追加locale;开启草稿/发布的类型会追加status、publicationFilter,以及为兼容旧客户端保留的hasPublishedVersion(源码注释明确其为 Deprecated,建议迁移到publicationFilter)。
路由前缀则在运行时统一拼装:packages/core/core/src/services/content-api/index.ts 的 getRoutesMap 会把 api.rest.prefix(默认 /api)拼接到每条路由的 path 上,插件路由则额外加上插件名前缀。这解释了为什么默认端点形如 /api/articles。
此外,Content API 还暴露了 addQueryParams / addInputParams 扩展点(content-api/index.ts#L181-L191),允许插件或应用注册自定义查询/输入参数(Zod schema 约束:查询参数必须是标量或标量数组,重名或占用 Strapi 保留键会在注册时直接抛错)。
2.2 中间件层:默认清单、必选清单与全局注册
Koa 应用本身在 packages/core/core/src/services/server/index.ts 中创建:它读取 server.proxy.*、server.app.keys 等配置构建 Koa 实例,将每个请求上下文放入 requestCtx(用于后续生命周期钩子的数据传递),并注册了健康检查端点 ALL /_health——返回 204 状态码和 strapi: You are so French! 响应头,可作为容器编排的存活探针。
Strapi 内置的中间件工厂集中在 packages/core/core/src/middlewares/index.ts,共 14 个:compression、cors、errors、favicon、ip、logger、poweredBy、body、query、responseTime、responses、security、session、public。
而“哪些默认生效”由 packages/core/core/src/services/server/register-middlewares.ts 中的两份清单决定:
- 默认配置(
middlewares配置缺省时的取值):strapi::logger→strapi::errors→strapi::security→strapi::cors→strapi::poweredBy→strapi::session→strapi::query→strapi::body→strapi::favicon→strapi::public; - 必选清单:
errors、security、cors、query、body、public、favicon缺一不可,若用户在config/middlewares中移除任何一项,checkRequiredMiddlewares会在启动阶段抛出错误; - 配置项本身支持两种写法:字符串(
"strapi::cors")或对象({ name, resolve, config }),对象形式可用于自定义中间件,注册前会用 Yup schema 严格校验格式。
这份源码证据恰好印证了 README 中“分层架构”的说法:中间件在路由之前全局挂载(strapi.server.use),负责错误兜底、安全头、CORS、请求体解析等横切关注点,请求随后才进入各路由的 Controller。
2.3 控制器与服务层:默认行为的“出厂设置”
控制器(controllers)与业务服务(services)按 API 目录约定加载。packages/core/core/src/loaders/apis.ts 的 loadAPI 会并行读取每个 API 目录下的 config、routes、controllers、services、policies、middlewares、content-types 七个子目录,并把文件名规范化为 kebab-case 作为键。也就是说,你在应用 src/api 下新建一个 API 文件夹并遵循这套目录约定,路由与权限体系就能自动接管它。
对外查询的默认行为在 packages/core/core/src/core-api/service/core-service.ts 中一目了然:
export abstract class CoreService {
getFetchParams(params = {}): any {
return {
status: 'published',
...params,
};
}
}
基类把 status: 'published' 作为默认查询条件——这正是“对外 API 只返回已发布内容”这一 Draft & Publish 语义的落地位置。集合/单例的控制器(core-api/controller/collection-type.ts、single-type.ts)继承该基类实现各自的 find/findOne/create/update/delete。
三、快速上手:安装、环境要求与 Docker
3.1 用 Quickstart 创建项目
README 给出的最快创建方式:
npx create-strapi@latest my-project
该命令生成一个带默认功能集(认证、权限、内容管理、内容类型构建器、文件上传)的项目。对应源码即本仓库的 packages/cli/create-strapi-app(Quickstart 模板位于其 templates/ 目录)。若你想先看完整可运行项目,仓库自带的 examples/getstarted、examples/complex、examples/empty 三个示例分别展示了“带示例数据”“复杂建模”“纯净骨架”三种形态,其中 examples/complex/scripts 目录还提供了 SQLite、PostgreSQL、MySQL、MariaDB 四种数据库的一键切换脚本(如 db-sqlite.ts),与 README 宣称的数据库选型一一对应。
3.2 环境要求
以当前仓库根 package.json 为准:
- Node.js:
>=20.0.0 <=26.x.x,npm>=6.0.0(engines字段); - 包管理器:
yarn@4.12.0(packageManager字段),且为 Yarn Workspaces monorepo,workspaces覆盖packages/*、packages/*/*、examples/*等; - 仓库通过
isStrapiMonorepo: true标记自身为 monorepo,构建/测试统一走 nx(build、test:unit、test:api、test:e2e等脚本均基于nx run-many)。
3.3 Docker 部署
README 明确说明:Strapi 不发布官方 Docker 镜像,需要基于自己的项目自行构建,并推荐社区工具:
npx @strapi-community/dockerize@latest
它会为你的项目生成定制的 Dockerfile 与 docker-compose.yml。仓库内也保留了开发用的 compose 文件供参考:docker-compose.dev.yml(开发)与 docker-compose.test.yml(测试)。生产环境则建议按官方 Docker 文档的思路自行维护镜像构建流程。
四、读懂仓库:monorepo 结构与贡献入口
README 的 “Repositories” 一节将 Strapi 生态分为三个仓库(核心 monorepo、Design System、LaunchPad 演示应用),而本文所在仓库正是核心 monorepo,其内部结构即 README.md 所述 CMS 本体的完整实现:
- packages/core:
strapi(CLI 入口与启动编排)、core(本文分析的请求流水线所在)、database、admin、content-manager、content-type-builder、permissions、types等核心能力包; - packages/plugins:
graphql、i18n、upload、users-permissions、sentry、documentation等可安装插件; - packages/providers:邮件(SES、Mailgun、SendGrid、Nodemailer、sendmail)与存储(AWS S3、Cloudinary、local)第三方 provider;
- packages/cli:
create-strapi、create-strapi-app、cloud等命令行工具; - packages/generators:基于 plop 的代码生成器;
- examples:可直接运行的示例应用;
- tests:API 集成测试(tests/api)、CLI 测试、Playwright E2E(tests/e2e)与版本迁移场景(tests/migration,根
package.json中test:migrations*脚本即驱动它)。
贡献与治理方面,仓库提供了 CONTRIBUTING.md(贡献指南)、SECURITY.md(安全漏洞负责任披露流程,对应 README 的 Security 一节)、LICENSE 与 CODE_OF_CONDUCT.md。官方文档站点源码也在本仓库内:docs/docs 下的 01-core、05-utils、guides、rfcs 等目录覆盖了核心概念、工具函数指南与设计 RFC,是比 README 更深一层的阅读材料。
五、小结:一条请求的完整旅程
把 README 的一句话架构与源码证据串起来,一个 GET /api/articles?filters[title]=$contains[xxx] 请求的旅程是:
- Koa 全局中间件按默认清单依次执行(logger → errors → security → cors → … → body → public),见 register-middlewares.ts;
- 请求命中 自动生成的路由契约(core-api/routes/index.ts),查询参数被 Zod schema 校验,
filters等合法键放行,未声明键按api.rest.strictParams策略处理; - 控制器(collection-type controller)接收请求,调用继承自 CoreService 的服务方法;
- 服务层以
status: 'published'为默认参数查询数据库,再经content-api.input/output两级 sanitizer 与权限过滤后返回。
这正是 Strapi “定义模型即拥有 API”的完整技术闭环:路由可预测、中间件可裁剪、控制器与服务可覆写——三层扩展点分别对应 routes、middlewares、controllers/services 目录约定(loaders/apis.ts),也是后续所有后端定制开发的出发点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00