本指南介绍如何在 NekroEdge 模板中创建和管理后端 API。
NekroEdge 使用 Hono 作为后端框架,结合 Zod 进行数据验证,Drizzle ORM 进行数据库操作。
- 🔒 类型安全: 端到端 TypeScript 类型检查
- 📖 自动文档: 基于 Zod Schema 自动生成 OpenAPI 文档
- ✅ 数据验证: 请求和响应自动验证
- 🗄️ ORM 集成: Drizzle ORM 提供类型安全的数据库操作
src/
├── db/
│ └── schema.ts # 数据库表定义
├── routes/
│ └── post.ts # API 路由实现
├── validators/
│ └── post.schema.ts # 数据验证 Schema
└── index.ts # 主入口,路由注册
// src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: integer("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
createdAt: integer("created_at").notNull(),
});// src/validators/user.schema.ts
import { z } from "zod";
export const CreateUserSchema = z.object({
name: z.string().min(1, "姓名不能为空"),
email: z.string().email("邮箱格式不正确"),
});
export const UpdateUserSchema = z.object({
name: z.string().min(1).optional(),
email: z.string().email().optional(),
});
export const UserParamsSchema = z.object({
id: z.string().transform((val) => parseInt(val)),
});// src/routes/user.ts
import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
import { users } from "../db/schema";
import { CreateUserSchema, UpdateUserSchema, UserParamsSchema } from "../validators/user.schema";
const app = new OpenAPIHono();
// 创建用户
const createUserRoute = createRoute({
method: "post",
path: "/users",
request: {
body: {
content: {
"application/json": {
schema: CreateUserSchema,
},
},
},
},
responses: {
201: {
description: "用户创建成功",
content: {
"application/json": {
schema: z.object({
id: z.number(),
name: z.string(),
email: z.string(),
createdAt: z.number(),
}),
},
},
},
},
tags: ["用户管理"],
});
app.openapi(createUserRoute, async (c) => {
const userData = c.req.valid("json");
const db = c.env.DB;
const result = await db
.insert(users)
.values({
...userData,
createdAt: Date.now(),
})
.returning();
return c.json(result[0], 201);
});
export default app;// src/index.ts
import userRoutes from "./routes/user";
// 在 apiApp 中注册路由
apiApp.route("/users", userRoutes);# 生成迁移文件
pnpm db:generate
# 应用迁移
pnpm db:migrate// 分页 Schema
const PaginationSchema = z.object({
page: z.string().transform((val) => Math.max(1, parseInt(val) || 1)),
limit: z.string().transform((val) => Math.min(100, Math.max(1, parseInt(val) || 10))),
});
// 实现分页查询
app.openapi(getUsersRoute, async (c) => {
const { page, limit } = c.req.valid("query");
const offset = (page - 1) * limit;
const users = await db.select().from(usersTable).limit(limit).offset(offset);
const total = await db.select({ count: sql`count(*)` }).from(usersTable);
return c.json({
data: users,
pagination: {
page,
limit,
total: total[0].count,
totalPages: Math.ceil(total[0].count / limit),
},
});
});// 自定义错误处理中间件
app.use("*", async (c, next) => {
try {
await next();
} catch (error) {
if (error instanceof ZodError) {
return c.json(
{
error: "数据验证失败",
details: error.errors,
},
400,
);
}
return c.json(
{
error: "服务器内部错误",
},
500,
);
}
});// JWT 认证中间件
const authMiddleware = async (c, next) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");
if (!token) {
return c.json({ error: "未提供认证令牌" }, 401);
}
try {
const payload = await verifyJWT(token);
c.set("user", payload);
await next();
} catch {
return c.json({ error: "无效的认证令牌" }, 401);
}
};
// 在需要认证的路由中使用
app.use("/users/*", authMiddleware);开发环境下访问:http://localhost:8787/api/doc
// src/index.ts
apiApp.doc("/doc", {
openapi: "3.0.0",
info: {
title: "NekroEdge API",
version: "1.0.0",
description: "全栈应用 API 文档",
},
tags: [
{ name: "用户管理", description: "用户相关操作" },
{ name: "内容管理", description: "内容相关操作" },
],
});// 使用 select 指定字段
const users = await db
.select({
id: usersTable.id,
name: usersTable.name,
email: usersTable.email,
})
.from(usersTable);
// 使用 with 进行条件查询
const activeUsers = await db.select().from(usersTable).where(eq(usersTable.active, true));// 数据库事务
await db.transaction(async (tx) => {
const user = await tx.insert(usersTable).values(userData).returning();
await tx.insert(profilesTable).values({
userId: user[0].id,
...profileData,
});
});# 创建用户
curl -X POST http://localhost:8787/api/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com"}'
# 获取用户列表
curl http://localhost:8787/api/users?page=1&limit=10// tests/api.test.ts
import { describe, it, expect } from "vitest";
describe("用户 API", () => {
it("应该能够创建用户", async () => {
const response = await fetch("http://localhost:8787/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "测试用户",
email: "test@example.com",
}),
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user.name).toBe("测试用户");
});
});// 添加 CORS 中间件
import { cors } from "hono/cors";
app.use(
"*",
cors({
origin: ["http://localhost:5175"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
}),
);// 获取环境变量
app.get("/config", async (c) => {
return c.json({
nodeEnv: c.env.NODE_ENV,
version: "1.0.0",
});
});- Schema 优先: 先定义 Zod Schema,再实现业务逻辑
- 一致性: 保持响应格式的一致性
- 错误处理: 提供清晰的错误信息和状态码
- 文档化: 为每个 API 添加清晰的描述和示例
- 类型安全: 充分利用 TypeScript 的类型检查