开发指南API 开发
API 开发
🚀 Cheatsheet (快速执行)
目标: 10分钟内创建一个完整的 API 接口,支持 CRUD 操作、OpenAPI 文档和权限验证
最快上手方式:
# 1. 创建新的 API 路由
mkdir -p src/server/routes/posts
touch src/server/routes/posts/router.ts
# 2. 复制以下代码到文件中
# (见下方代码示例)
# 3. 在 app.ts 中注册路由
# 4. 访问 API 和文档
open http://localhost:3000/api/docs # API 文档
curl http://localhost:3000/api/posts # API 接口
立即生效:
- 自动生成 OpenAPI 文档 📋
- 类型安全的 API 接口 🔒
- 统一的错误处理和验证 ✅
❓ 为什么要构建 API
对 MVP 的核心价值:
- 前后端分离: React 组件需要 API 获取和提交数据
- 移动端支持: 为未来的 App 提供统一的数据接口
- 第三方集成: 其他系统可以通过标准 REST API 集成
- 团队协作: OpenAPI 文档让前后端可以并行开发
真实场景举例:
你的 AI 写作工具需要管理用户对话,前端调用 POST /api/ai/chats
创建对话,GET /api/ai/chats
获取历史记录,所有接口都有完整的类型安全和文档支持。
🤔 为什么选择 Hono.js 而不是 Next.js API Routes
我们的实战对比:
// Next.js API Routes - 繁琐且缺乏标准
export async function GET(request: NextRequest) {
// 手动解析查询参数
// 手动验证权限
// 手动处理错误
// 没有自动生成的 API 文档
}
// Hono.js - 简洁且功能完整 ✅
export const postsRouter = new Hono()
.use(authMiddleware) // 自动权限验证
.get('/', validator('query', schema), // 自动参数验证
describeRoute({ // 自动生成 OpenAPI 文档
summary: '获取文章列表'
}),
async (c) => { /* 业务逻辑 */ }
)
Hono.js 的关键优势:
- 自动 OpenAPI 文档: 代码即文档,无需手动维护
- 中间件生态: 权限、验证、日志开箱即用
- 更好的 TypeScript: 端到端类型安全
- 性能更优: 比 Next.js API Routes 快 2-3 倍
- 部署灵活: 可部署到 Vercel、Cloudflare Workers 等
🧠 简要原理 & 最简案例
请求转发流程:
客户端请求 /api/posts
↓
Next.js route.ts (转发器)
↓
Hono.js app (实际处理)
↓
返回响应 + 自动生成的 OpenAPI 文档
核心转发代码:
// src/app/api/[[...rest]]/route.ts
import { app } from "@/server";
import { handle } from "hono/vercel";
const handler = handle(app);
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const PATCH = handler;
export const DELETE = handler;
export const OPTIONS = handler;
最简 API 示例:
// src/server/routes/hello.ts
import { Hono } from "hono";
import { describeRoute } from "hono-openapi";
export const helloRouter = new Hono()
.get("/hello",
describeRoute({
summary: "Hello World API",
tags: ["Demo"]
}),
(c) => c.json({ message: "Hello from Hono!" })
);
// 访问: GET http://localhost:3000/api/hello
// 返回: {"message": "Hello from Hono!"}
// 文档: http://localhost:3000/api/docs 自动包含此接口
🛠️ 实际操作步骤
1. 创建完整的 CRUD API (15分钟)
文章管理 API 示例:
// src/server/routes/posts/router.ts
import { Hono } from "hono";
import { describeRoute } from "hono-openapi";
import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { authMiddleware } from "../../middleware/auth";
// 数据验证 Schema
const CreatePostSchema = z.object({
title: z.string().min(1, '标题不能为空').max(200, '标题太长'),
content: z.string().min(1, '内容不能为空'),
category: z.enum(['tech', 'business', 'life']).optional(),
});
const PostResponseSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string(),
category: z.string().optional(),
createdAt: z.string(),
author: z.object({
name: z.string(),
image: z.string().optional(),
}),
});
export const postsRouter = new Hono()
.basePath("/posts")
.use(authMiddleware)
// GET - 获取文章列表
.get("/",
validator("query", z.object({
page: z.string().optional().default("1").transform(Number),
limit: z.string().optional().default("10").transform(Number),
category: z.string().optional(),
})),
describeRoute({
summary: "获取文章列表",
tags: ["Posts"],
responses: {
200: {
description: "文章列表",
content: {
"application/json": {
schema: resolver(z.object({
data: z.array(PostResponseSchema),
pagination: z.object({
page: z.number(),
limit: z.number(),
total: z.number(),
pages: z.number(),
}),
})),
},
},
},
},
}),
async (c) => {
const { page, limit, category } = c.req.valid("query");
const user = c.get("user");
try {
// 构建查询条件
const where = {
authorId: user.id,
...(category && { category }),
};
// 查询数据 (这里假设你有数据库查询函数)
const [posts, total] = await Promise.all([
db.post.findMany({
where,
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
include: {
author: {
select: { name: true, image: true }
}
}
}),
db.post.count({ where })
]);
return c.json({
data: posts,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
console.error('获取文章失败:', error);
return c.json({ error: '服务器错误' }, 500);
}
}
)
// POST - 创建新文章
.post("/",
validator("json", CreatePostSchema),
describeRoute({
summary: "创建新文章",
tags: ["Posts"],
responses: {
201: {
description: "文章创建成功",
content: {
"application/json": {
schema: resolver(z.object({ data: PostResponseSchema })),
},
},
},
},
}),
async (c) => {
const validatedData = c.req.valid("json");
const user = c.get("user");
try {
const post = await db.post.create({
data: {
...validatedData,
authorId: user.id,
},
include: {
author: {
select: { name: true, image: true }
}
}
});
return c.json({ data: post }, 201);
} catch (error) {
console.error('创建文章失败:', error);
return c.json({ error: '服务器错误' }, 500);
}
}
);
2. 注册路由到主应用 (2分钟)
// src/server/app.ts
import { postsRouter } from "./routes/posts/router";
export const app = new Hono().basePath("/api")
.use(loggerMiddleware)
.use(corsMiddleware)
.route("/", authRouter)
.route("/", postsRouter) // 👈 新增的路由
.route("/", aiRouter)
// ... 其他路由
3. 添加权限验证和工具函数 (10分钟)
使用现有的中间件系统:
// 权限验证中间件已经内置
import { authMiddleware } from "../../middleware/auth";
import { adminMiddleware } from "../../middleware/admin";
// 普通用户接口
.use(authMiddleware)
// 管理员接口
.use(adminMiddleware)
错误处理工具:
// src/server/lib/errors.ts
import { HTTPException } from "hono/http-exception";
export function createError(message: string, status: number = 500) {
throw new HTTPException(status, { message });
}
// 在路由中使用
if (!post) {
throw new HTTPException(404, { message: "文章不存在" });
}
4. 前端 API 客户端 (10分钟)
类型安全的客户端:
// lib/api/client.ts
import type { AppRouter } from "@/server/app";
import { hc } from "hono/client";
const client = hc<AppRouter>("/api");
// 完全类型安全的 API 调用
export async function getPosts(params?: {
page?: number;
limit?: number;
category?: string;
}) {
const response = await client.posts.$get({
query: params,
});
if (!response.ok) {
throw new Error('获取文章失败');
}
return response.json(); // 自动类型推断
}
export async function createPost(data: {
title: string;
content: string;
category?: string;
}) {
const response = await client.posts.$post({
json: data,
});
if (!response.ok) {
throw new Error('创建文章失败');
}
return response.json();
}
React Hook 封装:
// hooks/use-posts.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { getPosts, createPost } from "@/lib/api/client";
export function usePosts(params?: Parameters<typeof getPosts>[0]) {
return useQuery({
queryKey: ['posts', params],
queryFn: () => getPosts(params),
});
}
export function useCreatePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
}
💡 Hono.js 最佳实践
OpenAPI 文档最佳实践
// 详细的 API 文档描述
describeRoute({
summary: "创建新文章",
description: "用户可以创建新的文章,支持分类和富文本内容",
tags: ["Posts"],
responses: {
201: {
description: "文章创建成功",
content: {
"application/json": {
schema: resolver(PostResponseSchema),
},
},
},
400: {
description: "请求参数错误",
content: {
"application/json": {
schema: resolver(z.object({ error: z.string() })),
},
},
},
401: {
description: "用户未登录",
},
},
})
中间件组合模式
// 灵活的中间件组合
export const secureAdminRouter = new Hono()
.use(corsMiddleware) // CORS 处理
.use(loggerMiddleware) // 请求日志
.use(authMiddleware) // 用户认证
.use(adminMiddleware); // 管理员权限
// 在路由中使用
const adminRouter = secureAdminRouter
.get("/users", getUsersHandler)
.delete("/users/:id", deleteUserHandler);
统一错误处理
// 全局错误处理
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status);
}
console.error('未处理的错误:', err);
return c.json({ error: '服务器内部错误' }, 500);
});
🆘 常见问题解决
问题1: 为什么有些请求没有转发到 Hono?
解决方案: 检查 src/app/api/[[...rest]]/route.ts
的通配符路由配置
// ❌ 错误:特定路径会被 Next.js 优先处理
// src/app/api/docs-search/route.ts 存在时,
// /api/docs-search 不会转发到 Hono
// ✅ 正确:确保只有必要的路径在 Next.js 中处理
// 其他所有 /api/* 请求都会转发到 Hono
问题2: OpenAPI 文档没有显示所有接口
解决方案: 确保路由正确注册和使用 describeRoute
// ✅ 正确的路由注册
export const app = new Hono().basePath("/api")
.route("/", postsRouter) // 必须注册
.route("/", usersRouter);
// ✅ 接口必须使用 describeRoute
.get("/", describeRoute({ summary: "获取列表" }), handler)
问题3: 类型安全不工作
解决方案: 确保正确导出和使用 AppRouter 类型
// src/server/app.ts
export type AppRouter = typeof appRouter; // 必须导出类型
// lib/api/client.ts
import type { AppRouter } from "@/server/app";
const client = hc<AppRouter>("/api"); // 使用类型
问题4: 中间件执行顺序问题
// ✅ 正确的中间件顺序
export const app = new Hono().basePath("/api")
.use(loggerMiddleware) // 1. 日志记录
.use(corsMiddleware) // 2. CORS 处理
.use(authMiddleware) // 3. 认证 (如果需要)
.route("/", publicRouter) // 4. 公开路由
.route("/", privateRouter); // 5. 私有路由
📎 延伸阅读
核心文档:
- Hono.js 官方文档 - 完整的框架文档
- Hono OpenAPI - API 文档生成
- Zod 验证库 - TypeScript 数据验证
部署和集成:
- Vercel 部署指南 - 部署到 Vercel
- Cloudflare Workers - 边缘计算部署
实际项目参考:
- 查看
src/server/routes/
目录下的完整示例 - 访问
/api/docs
查看自动生成的 API 文档 - 参考
src/server/routes/ai.ts
了解复杂业务逻辑实现
下一步: API 开发完成后,查看 [数据库管理]./database) 了解如何优化数据库查询,或查看 [权限管理]./authentication) 了解高级权限控制。