Backend 指南
src/backend 实战指南:目录职责、api-client 在客户端/服务端的使用方式,以及完整请求链路。
这篇文档聚焦 MuseMVP 模板二次开发:重点不是讲概念,而是告诉你在这个代码库里“应该改哪里、怎么接链路”。
src/backend 目录快速地图
| 目录 | 职责 | 典型文件 |
|---|---|---|
src/backend/api | Hono API 入口、路由、中间件、OpenAPI | api/app.ts、api/routes/*、api/middleware/* |
src/backend/api-client | 前端可复用 API 调用层(含 hooks 与 server helper) | api-client.ts、admin/use-admin.ts、auth/use-auth-server.ts |
src/backend/auth | Better-Auth 服务端/客户端配置 | auth/auth.ts、auth/client.ts |
src/backend/database | Drizzle 连接、schema、queries | database/client.ts、database/schema.ts、database/queries/* |
关键边界
src/backend 负责“通用后端能力”;业务编排优先放在 src/modules/<feature>/lib/*,由 API 路由层调用。
一条请求是怎么走完的
客户端组件发起请求(通常通过 src/backend/api-client/* hooks)。
请求进入 /api/*,由 src/app/api/[[...rest]]/route.ts 转发给 Hono。
src/backend/api/app.ts 先执行全局中间件(logger、CORS),再匹配具体 router。
路由级中间件(如 authMiddleware、adminMiddleware)执行,handler 完成参数校验与响应封装。
Handler 调用 service(src/modules/*)或 query(src/backend/database/queries/*)。
数据库返回结果,响应回到前端;若是 mutation,前端用 TanStack Query 做缓存失效。
api-client 怎么用(客户端 vs 服务端)
客户端(Client Component)优先用 hooks
你会拿到现成的 query/mutation hooks(例如 useAdminUsersQuery、useAvatarUploadMutation),这是默认推荐方式:
"use client";
import { useAdminUsersQuery } from "@/backend/api-client/admin/use-admin";
export function AdminUsersPanel() {
const { data, isLoading, error } = useAdminUsersQuery({
itemsPerPage: 10,
currentPage: 1,
searchTerm: "",
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Load failed</div>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}如果要写新 hook,复用 apiClient + InferRequestType,避免手写请求类型:
import { useMutation } from "@tanstack/react-query";
import type { InferRequestType } from "hono";
import { apiClient } from "@/backend/api-client/api-client";
type ContactForm = InferRequestType<typeof apiClient.contact.$post>["form"];
export function useContactSubmit() {
return useMutation({
mutationFn: async (form: ContactForm) => {
const res = await apiClient.contact.$post({ form });
if (!res.ok) throw new Error("Submit failed");
},
});
}服务端(Server Component / Route Handler)有两种策略
- 同仓单体场景优先“直连服务/查询”(不绕一层 HTTP):
import { getSession } from "@/backend/api-client/auth/use-auth-server";
import { getActiveMuseBillingContractsByUserId } from "@/backend/database";
export async function getCurrentUserProState() {
const session = await getSession();
if (!session?.user?.id) return { hasProAccess: false };
const contracts = await getActiveMuseBillingContractsByUserId(session.user.id);
return { hasProAccess: contracts.length > 0 };
}- 必须从服务端调用
/api/*时,显式透传 cookie(常见于NextRequest场景):
import type { NextRequest } from "next/server";
import { getBaseUrl } from "@/lib/get-base-url";
export async function getMuseAccessStatusForSession(req: NextRequest) {
const response = await fetch(`${getBaseUrl()}/api/muse-billing/access`, {
headers: {
cookie: req.headers.get("cookie") || "",
},
});
if (!response.ok) return null;
return response.json();
}新增一个业务接口的推荐流程
在 src/backend/database/queries/* 写数据读写函数(只做 DB 访问)。
在 src/modules/<feature>/lib/* 写业务编排(规则、状态流转、异常处理)。
在 src/backend/api/routes/<feature>/* 写 Hono 路由:validator + middleware + handler。
到 src/backend/api/app.ts 注册 router。
在 src/backend/api-client/<feature>/* 提供前端调用 hook,并在 mutation 后做 invalidateQueries。
用 /api/openapi 或 /api/docs 检查接口描述是否符合预期。