文档
开发指南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. 私有路由

📎 延伸阅读

核心文档:

部署和集成:

实际项目参考:

  • 查看 src/server/routes/ 目录下的完整示例
  • 访问 /api/docs 查看自动生成的 API 文档
  • 参考 src/server/routes/ai.ts 了解复杂业务逻辑实现

下一步: API 开发完成后,查看 [数据库管理]./database) 了解如何优化数据库查询,或查看 [权限管理]./authentication) 了解高级权限控制。