Agent Skills: Nitro v3 接口开发技能规范

>-

UncategorizedID: ruan-cat/monorepo/nitro-api-development

Install this agent skill to your local

pnpm dlx add-skill https://github.com/ruan-cat/monorepo/tree/HEAD/ai-plugins/dev-skills/skills/nitro-api-development

Skill Files

Browse the full folder contents for nitro-api-development.

Download Skill

Loading file tree…

ai-plugins/dev-skills/skills/nitro-api-development/SKILL.md

Skill Metadata

Name
nitro-api-development
Description
>-

Nitro v3 接口开发技能规范

本技能用于指导使用 Nitro v3 框架编写服务端接口,包括项目初始化、配置、接口编写规范、数据库交互和多平台部署等完整流程。

1. 适用场景

  • 纯后端 Nitro 项目初始化:对非 Vite 的 Node.js 项目,初始化 Nitro 示例代码和配置
  • Vite 项目全栈化:对 Vite 项目,初始化 Nitro 接口和配置,赋予全栈能力
  • 接口开发与维护:按规范编写 Nitro v3 格式的接口代码
  • Mock 数据迁移到真实数据库:将旧的 Mock 接口迁移到 Drizzle ORM + 真实数据库
  • 多平台部署:适配 Cloudflare Worker、Vercel 等平台的环境变量和数据库连接

2. 核心原则 [CRITICAL]

  1. 框架 (Framework): 使用 Nitro v3H3 事件处理器 (defineHandler)。
  2. 数据库 (Database): 推荐使用 Drizzle ORM 进行所有数据库交互。新开发不建议使用 Mock JSON 文件
  3. 响应格式 (Response Format): 必须严格遵循 ApiResponsePageData 结构返回 { success, code, message, data }。类型定义参见 templates/types.ts
  4. 错误处理 (Error Handling): 所有 Handler 必须使用 try-catch 包裹全部业务逻辑,catch 块返回标准化错误响应。
  5. 无状态 (Stateless): 保持 API 处理器无状态,所有数据持久化必须通过数据库。

3. 核心依赖

# Nitro v3 核心包
pnpm add nitro

# 数据库相关(可选,推荐)
pnpm add drizzle-orm
pnpm add -D drizzle-kit

# 参数验证(可选,推荐)
pnpm add zod

# 日志工具(可选)
pnpm add consola

4. 目录结构规范

4.1 扁平结构(推荐用于小型项目)

project-root/
├── server/                          # Nitro 服务端目录
│   ├── routes/                      # API 路由目录
│   │   ├── users.get.ts             # GET /users
│   │   ├── users.post.ts            # POST /users
│   │   └── health.get.ts            # GET /health
│   ├── db/                          # 数据库相关(可选)
│   │   ├── index.ts                 # 数据库连接(useDb)
│   │   └── schema/                  # Drizzle Schema 定义
│   ├── utils/                       # 工具函数
│   │   └── format-date.ts           # 时间格式化(参见 templates/format-date.ts)
│   └── types/                       # 类型定义
│       └── index.ts                 # ApiResponse、PageData 等(参见 templates/types.ts)
├── nitro.config.ts                  # Nitro 配置文件
└── package.json

4.2 模块化结构(适用于大型项目)

project-root/
├── server/
│   ├── api/                         # API 接口目录
│   │   └── {module}/{feature}/
│   │       ├── list.post.ts         # 列表查询接口
│   │       ├── create.post.ts       # 创建接口
│   │       ├── update.post.ts       # 更新接口
│   │       ├── delete.post.ts       # 删除接口
│   │       └── [id].get.ts          # 详情接口
│   ├── db/                          # 数据库连接和 Schema
│   └── utils/                       # 工具函数
├── nitro.config.ts
└── package.json

文件路径映射规则:文件路径直接映射为 API 路径

文件: server/routes/users.get.ts     -> GET /users
文件: server/api/users/list.post.ts  -> POST /api/users/list
文件: server/api/users/[id].get.ts   -> GET /api/users/:id

5. 接口编写规范 [CRITICAL]

5.1 导入模块规范

// ✅ 必须从 nitro/h3 导入,不是 h3
import { defineHandler, readBody } from "nitro/h3";

// ✅ 使用 defineHandler,不是 defineEventHandler
export default defineHandler(async (event) => {
	// ...
});

5.2 返回值类型约束

import type { ApiResponse }不够的——这只是一个死导入,TypeScript 不会检查返回值结构。

必须ApiResponse 用作响应变量的类型注解 (type annotation)

// ❌ 错误:仅导入类型,直接返回字面量 → TypeScript 不做任何检查
import type { ApiResponse } from "./types";
return { success: true, code: 200, msg: "ok", data: result }; // msg 拼错也不会报错

// ✅ 正确:用类型注解标注响应变量 → TypeScript 会严格检查每个字段
import type { ApiResponse } from "./types";
const response: ApiResponse<typeof result> = { success: true, code: 200, message: "ok", data: result };
return response; // 如果字段名/类型不符合 ApiResponse,编译期立即报错

5.3 按端点类型的类型注解规则

| 端点类型 | 类型注解写法 | | :------------------------------- | :--------------------------------------------: | | 分页列表(list) | ApiResponse<PageData<(typeof data)[number]>> | | 单条数据(detail/create/update) | ApiResponse<typeof result> | | 无数据返回(delete) | ApiResponse<null> | | 错误响应(catch 块) | ApiResponse<null> |

(typeof data)[number] 自动从 Drizzle 查询结果数组推断行类型,无需额外导入实体类型。

6. 开发工作流

  1. 定义路由 (Define Route): 在 server/api/server/routes/ 创建文件。文件路径即 API 路由。
  2. 实现处理器 (Implement Handler): 使用 defineHandler 定义处理函数,必须使用 try-catch 包裹。
  3. 导入类型约束 (Import Types): 从你的类型文件导入 ApiResponse(列表接口额外导入 PageData)。
  4. 查询数据库 (Query Database): 导入 db 与 schema,使用 Drizzle 查询构建器。
  5. 返回数据 (Return Data): 确保返回对象严格符合 ApiResponse<T> 结构。

6.1 纯 Nitro v3 favicon 路由

纯 Nitro API 的 favicon 实现、浏览器 fallback 和配套测试见 references/favicon.md。核心边界是:图标放在 public/favicon.svgserver/routes/favicon.ico.get.ts 返回 redirect("/favicon.svg", 302),并在 tests/favicon-route.test.ts 锁定重定向契约。

7. 时间字段格式化

7.1 核心原则

数据库 Schema 中的时间字段使用 Drizzle timestamp 类型,TypeScript 推断为 Date 类型。前端展示需要 string 类型。API Handler 负责时间字段的格式化转换

7.2 使用 formatDateTime 工具函数

必须使用共享的时间格式化工具函数(参见 templates/format-date.ts),禁止在 Handler 内重复定义格式化函数。

import { formatDateTime } from "server/utils/format-date"; // 根据你的项目结构调整

const list = data.map((item) => ({
	id: item.id,
	name: item.name,
	createTime: formatDateTime(item.createTime),
	updateTime: formatDateTime(item.updateTime),
}));

8. 类型回填 (Type Recovery)

readValidatedBody 的类型推导不足以满足 Drizzle values() 的严格类型要求时,必须显式回填 Insert 类型。

// 从你的 Schema 文件导入 Insert 类型
import type { NewYourEntity } from "./schema";

const body = (await readValidatedBody(event, insertSchema.parse)) as unknown as NewYourEntity;
const result = await db.insert(table).values(body).returning();

9. 多平台数据库连接

本节基于 Cloudflare Worker 环境下排查真实严重 Bug 后沉淀的核心经验。

9.1 核心原则:永远通过 useDb(event) 获取数据库实例

严禁在模块顶层或全局作用域直接创建 Drizzle 数据库连接实例:

// ❌ 错误:模块顶层创建,Cloudflare Worker 环境下 process.env 为空
const db = drizzle(neon(process.env.DATABASE_URL!));

// ✅ 正确:在每个 handler 内通过 event 动态获取
export default defineHandler(async (event) => {
	const db = useDb(event); // 内部自动处理多平台环境变量
	return await db.select().from(table);
});

9.2 Cloudflare Worker 的核心陷阱

在 Nitro v3 + Cloudflare Worker 环境中,event.context.cloudflare.env 不存在。 正确路径必须是 event.req.runtime?.cloudflare?.env(Nitro v3 官方确认路径)。

详细内容请参考references/cloudflare-env-database.md

10. Nitro 配置

10.1 最短执行路径 [CRITICAL]

先只完成以下路由,再进入目录、接口、数据库或部署章节。不要从 10.5 的 Cloudflare 片段开始新建 nitro.config.ts

  1. 读取 package.json 的 scripts 与依赖;存在 vite.config.* 时,再读取该文件。
  2. 按 10.2 的判定表选择唯一分支:Vite 全栈、纯 Nitro API,或停止并向用户确认。
  3. 纯 Nitro API:以 10.3 的完整配置为唯一复制起点;再按需要合并 10.5 的部署片段。
  4. Vite 全栈:按 10.4 把 nitro() 加入已有 Vite 配置;不要误套用纯 API 的 OpenAPI 配置。
  5. 仅在纯 Nitro API 分支完成后,执行 10.3 的快速核对。

10.2 项目类型判定 [CRITICAL]

配置前必须先判断项目是 Vite 全栈项目 还是 纯 Nitro API 项目,不能因为仓库中同时出现 server/ 目录就直接套用其中一种配置。

| 已确认的证据 | 选择的分支 | 下一步 | | :-------------------------------------------------------------------------------- | :----------- | :--------------- | | vite.config.* 导入并使用 nitro/vitenitro() | Vite 全栈 | 按 10.4 集成 | | scripts 以 nitro dev / nitro build 启动和构建,且不存在上述 nitro/vite 集成 | 纯 Nitro API | 按 10.3 完整配置 | | 同时满足两类证据、只有 vite 依赖、或 scripts/配置用途无法确认 | 不判定 | 停止并向用户确认 |

不要仅凭 server/ 目录、单独安装了 vite,或单独安装了 nitro 判定项目类型。

10.3 纯 Nitro API 基础配置 [CRITICAL]

纯 Nitro API 项目的 nitro.config.ts 必须从 package.json 读取版本号,并固定启用 OpenAPI、/openapi.json/scalar。如果 package.json 没有 version,先在该文件补充有效的 SemVer 版本(项目没有既有发版约定时使用 0.1.0),再使用 pkg.version;不得在配置中写死文档版本。openAPI.meta.titledescription 必须改为当前项目的名称和接口用途,不能保留示例文本。

compatibilityDate 必须使用包含部署平台的对象形式,并保留每个部署平台官方说明链接的行内注释。禁止退化为单字符串,即使当前只部署一个平台也仍保留 Cloudflare 与 Vercel 两项,以便人类阅读、学习和后续维护。

import { defineConfig } from "nitro";
import pkg from "./package.json" with { type: "json" };

export default defineConfig({
	serverDir: "server",
	imports: false,
	compatibilityDate: {
		// https://v3.nitro.build/deploy/providers/cloudflare
		cloudflare: "2024-09-19",
		// https://nitro.build/deploy/providers/vercel#observability
		vercel: "2024-09-19",
	},
	experimental: {
		openAPI: true,
	},
	openAPI: {
		meta: {
			title: "your-project API",
			description: "Nitro v3 接口文档",
			version: pkg.version,
		},
		production: "prerender",
		route: "/openapi.json",
		ui: {
			scalar: {
				route: "/scalar",
			},
		},
	},
	devServer: {
		port: 3000,
	},
});

完成纯 API 配置后,只做以下两项快速核对,避免引入专用脚本或测试工程:

node -p "require('./package.json').version"
rg -n "compatibilityDate|cloudflare:|vercel:|openAPI:|version: pkg.version|/openapi.json|/scalar" nitro.config.ts

第一条命令必须输出有效版本;第二条命令的输出必须包含上述配置键。命令不替代人工检查 compatibilityDate 的两个官方链接注释。

CRITICAL:Nitro v3 的 serverDir 配置陷阱

Nitro v3 默认从项目根目录扫描 routes/plugins/middleware/。当服务端代码放在 server/ 子目录时,必须在 nitro.config.ts 中设置 serverDir: "server";否则这些路由不会被构建打包,构建产物中不包含端点,访问时会全部返回 404。Nitro v2 默认扫描 server/,升级到 v3 时尤其容易遗漏此配置。

10.4 Vite 集成

// vite.config.ts
import { nitro } from "nitro/vite";

export default defineConfig({
	plugins: [
		// 其他插件...
		nitro(),
	],
});

10.5 Cloudflare 部署配置片段

把下列键合并到现有的 defineConfig({ ... }) 内;这不是独立的 nitro.config.ts,不能单独复制。纯 Nitro API 项目必须从 10.3 的完整配置开始,再合并本片段,因此不会遗漏 OpenAPI、pkg.versioncompatibilityDate

cloudflare: {
	deployConfig: true,
	nodeCompat: true,
	wrangler: {
		name: "your-project-name",
	},
},
// 如果使用 cloudflare:workers 动态导入
rollupConfig: {
	external: ["cloudflare:workers"],
},

11. 常见陷阱 (Common Pitfalls)

  • 错误的导入源: 必须从 nitro/h3 导入,而非 h3。必须使用 defineHandler 而非 defineEventHandler
  • 缺失类型注解: 仅导入类型不够,必须使用类型注解约束响应变量。
  • 错误的响应字段: 前端依赖 { success, code, message, data } 结构。使用 msg 而非 message 会导致前端解析异常。
  • 缺失 try-catch: 所有 Handler 必须使用 try-catch 包裹,catch 块返回标准化错误响应。
  • 遗漏 Await: 数据库操作是异步的,必须使用 await
  • 使用原始 SQL: 除非万不得已,禁止使用 sql 模板字符串。请使用 Drizzle 的查询构建器。
  • 重复定义格式化函数: 必须使用共享的工具函数,禁止在 Handler 内重复定义 formatDateTime
  • 模块顶层创建数据库连接: Cloudflare Worker 环境下会导致连接失败。必须在 handler 内创建。
  • 错误的 Cloudflare 环境变量路径: Nitro v3 使用 event.req.runtime?.cloudflare?.env,而非 event.context.cloudflare?.env
  • serverDir 配置遗漏: Nitro v3 默认扫描项目根目录的 routes/plugins/middleware/。若代码位于 server/ 子目录,必须配置 serverDir: "server",否则路由不会进入构建产物,所有端点返回 404。
  • compatibilityDate 写成单字符串: 必须保留 Cloudflare、Vercel 两个平台键和对应官方链接注释的对象形式;单字符串丢失了部署适配信息。
  • 纯 Nitro API 未暴露接口文档: 判定为纯 API 项目后,必须启用 experimental.openAPI,提供 /openapi.json/scalar,并从 package.json 读取 openAPI.meta.version
  • 纯 Nitro API 缺少 favicon fallback: 只添加 public/favicon.svg 不足以覆盖浏览器默认的 /favicon.ico 请求,必须增加 server/routes/favicon.ico.get.ts 并测试重定向。
  • Nitro v3 使用已弃用的 sendRedirect: 改为 return redirect(location, status),并为 fallback 路由保留状态码和 Location 断言。

12. 常见错误对比

| 错误写法 | 正确写法 | | :---------------------------------------- | :---------------------------------------------- | | import { defineEventHandler } from "h3" | import { defineHandler } from "nitro/h3" | | export default defineEventHandler(...) | export default defineHandler(...) | | 直接返回对象无类型注解 | 使用 const response: ApiResponse<T> = ... | | process.env.DATABASE_URL(CF Worker) | event.req.runtime?.cloudflare?.env | | 模块顶层 const db = drizzle(...) | handler 内 const db = useDb(event) | | 单字符串形式的 compatibilityDate | 带 Cloudflare、Vercel 键及官方链接注释的对象 | | 纯 API 不配置接口文档 | 启用 OpenAPI,暴露 /openapi.json/scalar |

13. 项目初始化检查清单

13.1 纯后端项目

  • [ ] 按 10.2 的判定表确认这是纯 Nitro API 项目,而非 Vite 全栈项目
  • [ ] 安装 nitro 依赖包
  • [ ] 创建 server/routes/ 目录结构
  • [ ] 创建 nitro.config.ts 配置文件
  • [ ] 核对 nitro.config.ts 已设置 serverDir: "server",确保 server/ 下的路由、插件和中间件会被构建打包
  • [ ] 核对 compatibilityDate 是带 Cloudflare、Vercel 平台键和官方链接注释的对象,不是单字符串
  • [ ] 确认 package.json 具有有效 version,并通过 pkg.version 设置 openAPI.meta.version
  • [ ] 启用 experimental.openAPI,配置 /openapi.json/scalar,并为 openAPI.meta 填写项目对应的标题和说明
  • [ ] 添加开发和构建脚本到 package.json
  • [ ] 创建 server/types/ 并复制 templates/types.ts 中的类型定义

13.2 Vite 项目全栈化

  • [ ] 按 10.2 的判定表确认由 Vite 负责前端构建,再采用 nitro/vite 集成方式
  • [ ] 安装 nitro 依赖包
  • [ ] 在 Vite 插件配置中添加 nitro() 插件
  • [ ] 创建 server/ 目录结构
  • [ ] 创建 nitro.config.ts 配置文件
  • [ ] 核对 nitro.config.ts 已设置 serverDir: "server",确保 server/ 下的路由、插件和中间件会被构建打包
  • [ ] 创建 server/types/ 并复制 templates/types.ts 中的类型定义

13.3 数据库集成

14. 参考文档

详细的代码模板和参考文档请查阅: