About nitro-api-development
A development guide for building and maintaining server APIs with Nitro v3. It covers setup for standalone Nitro and Vite projects, handler and response conventions, Drizzle database access, and deployment considerations for multiple platforms.
Key features
- 01Initialize standalone Nitro or Vite full-stack projects
- 02Follow route and defineHandler conventions
- 03Enforce response shapes and types
- 04Use Drizzle for database operations
- 05Configure Cloudflare and Vercel deployments
- 06Consult migration, testing, and code templates
Use cases
- 01Set up a Nitro API in a Node.js project
- 02Extend a Vite project with server APIs
- 03Migrate Mock endpoints to a real database
- 04Diagnose database connections on Cloudflare Worker
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ ruan-cat/ monorepo --skill nitro-api-developmentAfter installing, type/nitro-api-developmentin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api-reference.mdcloudflare-env-database.mdexamples.mdfavicon.mdmock-mode.mdmock-to-database-migration.mdrequest-params-handling.mdvitest-testing.mdtemplates/format-date.tstypes.tsDetails
From the project READMENitro v3 接口开发技能规范
本技能用于指导使用 Nitro v3 框架编写服务端接口,包括项目初始化、配置、接口编写规范、数据库交互和多平台部署等完整流程。
1. 适用场景
- 纯后端 Nitro 项目初始化:对非 Vite 的 Node.js 项目,初始化 Nitro 示例代码和配置
- Vite 项目全栈化:对 Vite 项目,初始化 Nitro 接口和配置,赋予全栈能力
- 接口开发与维护:按规范编写 Nitro v3 格式的接口代码
- Mock 数据迁移到真实数据库:将旧的 Mock 接口迁移到 Drizzle ORM + 真实数据库
- 多平台部署:适配 Cloudflare Worker、Vercel 等平台的环境变量和数据库连接
2. 核心原则 [CRITICAL]
- 框架 (Framework): 使用 Nitro v3 和 H3 事件处理器 (
defineHandler)。 - 数据库 (Database): 推荐使用 Drizzle ORM 进行所有数据库交互。新开发不建议使用 Mock JSON 文件。
- 响应格式 (Response Format): 必须严格遵循
ApiResponse和PageData结构返回{ success, code, message, data }。类型定义参见 templates/types.ts。 - 错误处理 (Error Handling): 所有 Handler 必须使用
try-catch包裹全部业务逻辑,catch 块返回标准化错误响应。 - 无状态 (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. 开发工作流
- 定义路由 (Define Route): 在
server/api/或server/routes/创建文件。文件路径即 API 路由。 - 实现处理器 (Implement Handler): 使用
defineHandler定义处理函数,必须使用try-catch包裹。 - 导入类型约束 (Import Types): 从你的类型文件导入
ApiResponse(列表接口额外导入PageData)。 - 查询数据库 (Query Database): 导入
db与 schema,使用 Drizzle 查询构建器。 - 返回数据 (Return Data): 确保返回对象严格符合
ApiResponse<T>结构。
6.1 纯 Nitro v3 favicon 路由
纯 Nitro API 的 favicon 实现、浏览器 fallback 和配套测试见 references/favicon.md。核心边界是:图标放在 public/favicon.svg,server/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。
- 读取
package.json的 scripts 与依赖;存在vite.config.*时,再读取该文件。 - 按 10.2 的判定表选择唯一分支:Vite 全栈、纯 Nitro API,或停止并向用户确认。
- 纯 Nitro API:以 10.3 的完整配置为唯一复制起点;再按需要合并 10.5 的部署片段。
- Vite 全栈:按 10.4 把
nitro()加入已有 Vite 配置;不要误套用纯 API 的 OpenAPI 配置。 - 仅在纯 Nitro API 分支完成后,执行 10.3 的快速核对。
10.2 项目类型判定 [CRITICAL]
配置前必须先判断项目是 Vite 全栈项目 还是 纯 Nitro API 项目,不能因为仓库中同时出现 server/ 目录就直接套用其中一种配置。
| 已确认的证据 | 选择的分支 | 下一步 |
|---|---|---|
vite.config.* 导入并使用 nitro/vite 的 nitro() | 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.title 与 description 必须改为当前项目的名称和接口用途,不能保留示例文本。
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.version 或 compatibilityDate。
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 数据库集成
- 安装
drizzle-orm和对应的数据库驱动 - 安装
drizzle-kit(开发依赖) - 创建
server/db/index.ts(参考 references/cloudflare-env-database.md 中的useDb实现) - 创建
server/db/schema/目录并定义数据表 - 复制 templates/format-date.ts 到
server/utils/
14. 参考文档
详细的代码模板和参考文档请查阅:
- 可复用类型定义: templates/types.ts -
ApiResponse<T>、PageData<T>等通用类型定义,可直接复制使用 - 时间格式化工具: templates/format-date.ts -
formatDateTime、formatDate工具函数 - API 语法速查: references/api-reference.md - H3 常用函数及模式速查
- 代码示例: references/examples.md - 标准的 CRUD 处理器示例和响应结构
- 参数处理: references/request-params-handling.md -
readBody使用、参数清洗和 Zod 校验 - 多平台数据库连接: references/cloudflare-env-database.md - Cloudflare Worker 与 Vercel 环境变量获取和数据库连接模式
- 迁移指南: references/mock-to-database-migration.md - 从 Mock 数据迁移到真实数据库
- Mock 模式参考 (Legacy): references/mock-mode.md - Legacy Mock 模式的开发规范(仅用于维护现有接口)
- 接口测试: references/vitest-testing.md - Vitest + Nitro 接口测试配置和模板
- 官方文档: https://v3.nitro.build/