Wasp 自定义 HTTP API 端点(Custom APIs)完全指南:从声明到部署的实践详解
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
导读
Wasp 默认通过 Operations(Query / Action)实现客户端与服务器之间的交互,但当你需要精确控制 HTTP 方法(method)、URL 路径(path)或响应格式时,Operations 往往显得力不从心。本文以 Wasp 0.15 文档中的 Custom HTTP API Endpoints 为核心,系统讲解如何在 Wasp 中声明并实现自定义 API 端点、如何在客户端与外部世界调用它们、如何处理 CORS 与认证,以及如何在 API 中使用 Prisma Entity。读完本文,你将能够完全掌控 Express 层的路由细节,写出带完整类型安全的自定义 HTTP 接口。
什么是 Wasp API:与 Operations 的分工
在 Wasp 中,客户端与服务器之间的默认交互机制是 Operations——也就是 Query 与 Action。它们由 Wasp 自动生成类型安全的客户端辅助函数(如useQuery),并替你处理序列化、错误封装等大量细节。
但并非所有场景都适合 Operations:
- 你需要一个特定的 HTTP 方法与路径组合,例如
POST /webhook/callback; - 你需要自定义响应格式(原始 JSON、文件流、特定状态码);
- 你需要对接外部系统(Webhook 回调、第三方服务轮询等)。
在这些场景下,Wasp 提供api声明。它本质上是"将一个 Node.js 函数绑定到某个 HTTP 端点"(例如POST /something/special)。与 Operations 最大的区别是:API 没有客户端辅助函数(如useQuery),你需要自己通过 Axios 包装器或任意 HTTP 客户端发起请求。
从源码结构看,Wasp 在生成服务器代码时,会为每个api声明生成一段 Express 路由注册逻辑:waspc/data/Generator/templates/server/src/routes/apis/index.ts 中可以看到,每个 API 通过router.METHOD(path, ...middleware, defineHandler(...))挂载到同一个 Express Router 上。这意味着你写的每个 API 最终就是一个标准的 Express 路由处理器,Wasp 只负责把路由、中间件和上下文(context)组装好交给你。
如何创建一个 API
创建一个 Wasp API 只需两步:
- 在 Wasp 文件中用
api声明定义 API; - 在
src目录中实现对应的 Node.js 函数。
完成这两步后,你就可以从客户端代码(通过 Wasp 提供的 Axios 包装器)或从外部世界调用这个 API 了。
第一步:在 Wasp 文件中声明 API
在main.wasp(或 TS Spec 文件中)中使用api声明:
// ... api fooBar { // API 与实现函数不必同名(但可以同名) fn: import { fooBar } from "@src/apis", httpRoute: (GET, "/foo/bar") }注意两个要点:
fn指向实现函数在@src下的导入路径;httpRoute是一个(HttpMethod, string)元组:HTTP 方法与 Express 风格的路径字符串。
TypeScript 提示:为了让 Wasp 编译器为 API 生成对应的类型(例如
FooBar),你需要先把api声明添加到 Wasp 文件中,并且保持wasp start命令处于运行状态。Wasp 会根据声明自动生成wasp/server/api中的类型。
第二步:实现 Node.js 函数
API 的实现是一个接收三个参数的 Node.js 函数:
req:Express 的 Request 对象;res:Express 的 Response 对象;context:由 Wasp 注入的额外上下文对象,包含用户会话信息以及你声明的 Entity 信息。
最简单的 JavaScript 实现:
export const fooBar = (req, res, context) => { res.set("Access-Control-Allow-Origin", "*"); // 示例:修改响应头以覆盖 Wasp 默认 CORS 中间件 res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` }); };TypeScript 版本使用 Wasp 生成的类型:
import { FooBar } from "wasp/server/api"; // 该类型由 Wasp 根据上面的 api 声明生成 export const fooBar: FooBar = (req, res, context) => { res.set("Access-Control-Allow-Origin", "*"); // 示例:修改响应头以覆盖 Wasp 默认 CORS 中间件 res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` }); };在上面的示例中,res.set("Access-Control-Allow-Origin", "*")展示了如何修改响应头来覆盖 Wasp 默认的 CORS 中间件行为——这是 API 与 Operations 的一个重要差异点(详见下文 CORS 处理 小节)。
TypeScript 进阶:为 API 提供额外类型信息
Wasp 生成的FooBar类型支持两个泛型参数——params和response,从而在实现中获得完整的类型安全。假设我们要创建一个GET路由,从路径参数中取出 email 并返回"生命、宇宙以及一切事物的答案":
api fooBar { fn: import { fooBar } from "@src/apis", entities: [Task], httpRoute: (GET, "/foo/bar/:email") }import { FooBar } from "wasp/server/api"; export const fooBar: FooBar< { email: string }, // params { answer: number } // response > = (req, res, _context) => { console.log(req.params.email); // 类型安全地访问路径参数 res.json({ answer: 42 }); };这里FooBar<{ email: string }, { answer: number }>中的第一个泛型约束了req.params的类型,第二个泛型则描述响应体的结构,让 IDE 自动补全与编译期检查贯穿整个实现过程。
使用 API:外部调用与客户端调用
从外部使用 API
API 天然可以被"外部世界"调用:只要你的应用在运行,任何能发 HTTP 请求的客户端都可以按声明的(method, path)直接访问。例如应用运行在https://example.com,那么你可以通过浏览器、Postman、curl或另一个 Web 服务发起GET https://example.com/foo/bar请求。
从客户端使用 API
Wasp 为客户端提供了 Axios 包装器wasp/client/api,它在请求中自动附加认证信息。用法示例:
import React, { useEffect } from "react"; import { api } from "wasp/client/api"; async function fetchCustomRoute() { const res = await api.get("/foo/bar"); console.log(res.data); } export const Foo = () => { useEffect(() => { fetchCustomRoute(); }, []); return <>// ...</>; };TypeScript 版本完全一致,只是后缀变为.tsx:
import React, { useEffect } from "react"; import { api } from "wasp/client/api"; async function fetchCustomRoute() { const res = await api.get("/foo/bar"); console.log(res.data); } export const Foo = () => { useEffect(() => { fetchCustomRoute(); }, []); return <>// ...</>; };在真实仓库中,examples/kitchen-sink示例应用完整演示了这一模式:examples/kitchen-sink/src/features/apis/pages/ApisPage.tsx 中通过useQuery结合api.get(endpoint).json()分别请求了需要认证的/foo/bar与无需认证的/bar/baz两个端点,并用data-testid区分渲染结果。这说明"客户端调用自定义 API"在 Wasp 中就是一行api.get(...)的事,剩下的交给 React Query 管理加载态与错误态。
确保 CORS 正常工作
API 被设计得尽可能灵活,因此它们不像 Operations 那样默认启用默认中间件。要在客户端正常调用这些 API,你必须确保 CORS(跨域资源共享)已启用。
解决办法是在 Wasp 文件中为 API 定义自定义中间件。最简单的方式是使用apiNamespace声明——它把某个middlewareConfigFn应用到指定路径前缀下的所有API:
apiNamespace fooBar { middlewareConfigFn: import { fooBarNamespaceMiddlewareFn } from "@src/apis", path: "/foo" }实现文件中(这里返回默认配置,它即为/foo路径下的所有 API 启用了 CORS):
import { MiddlewareConfigFn } from "wasp/server"; export const apiMiddleware: MiddlewareConfigFn = (config) => { return config; };返回默认中间件配置即可为/foo路径下的所有 API 启用 CORS。关于中间件配置的更多信息,参见文档 Middleware Configuration。
在 API 中使用 Entities
很多 API 需要访问数据库资源,也就是 Entities。要在一个 API 中使用 Entity,只需把它加入api声明的entities字段:
api fooBar { fn: import { fooBar } from "@src/apis", entities: [Task], // 让 Task Entity 可用 httpRoute: (GET, "/foo/bar") }Wasp 会把声明的 Entity 注入 API 的context参数,让你直接访问该 Entity 的 Prisma API:
export const fooBar = (req, res, context) => { res.json({ count: await context.entities.Task.count() }); };TypeScript 版本:
import { FooBar } from "wasp/server/api"; export const fooBar: FooBar = (req, res, context) => { res.json({ count: await context.entities.Task.count() }); };context.entities.Task暴露的就是prisma.task——完整继承了 Prisma CRUD API(findMany、create、update、count、delete等)。
从服务器生成模板 waspc/data/Generator/templates/server/src/routes/apis/index.ts 的源码可以看出这一注入机制的具体实现:生成器会为每个 API 构造一个context对象,把usesAuth时的用户信息(makeAuthUserIfPossible(req.user))与声明过的实体映射(entities: { Task: prisma.task })打包在一起,再传给你的实现函数。这解释了为什么在 API 中"声明即得"——Wasp 在编译期替你完成了实体到 Prisma Client 的接线。
在 API 中使用认证(Auth)
Wasp API 也支持认证。将auth字段加入声明即可:
api fooBar { fn: import { fooBar } from "@src/apis", httpRoute: (GET, "/foo/bar"), entities: [Task], auth: true, // 解析 Authorization 头中的 JWT,向 context.user 注入用户 middlewareConfigFn: import { apiMiddleware } from "@src/apis" }auth字段的行为规则:
- 当应用中启用了认证时,
auth默认为true,此时 Wasp 会解析Authorization头中的 JWT,并向context.user注入解析出的用户对象; - 如果你不希望解析 JWT(例如这个 API 本身就是公开的,或你打算自己处理认证逻辑),请显式设为
false。
结合生成模板源码看:当usesAuth为真时,路由会被注册为[auth, ...apiMiddleware],即先经过 Wasp 的认证中间件再做业务处理;context.user则由makeAuthUserIfPossible构造。这一点在 waspc/data/Generator/templates/server/src/routes/apis/index.ts 中清晰可见。
examples/kitchen-sink中 examples/kitchen-sink/src/features/apis/apis.ts 的实现给出了一个实用的认证检查模式:fooBar先判断context.user是否存在,不存在则返回401 Unauthorized,存在则通过context.user.getFirstProviderUserId()拿到用户名:
export const fooBar: FooBar = (_req, res, context) => { if (!context.user) { res.status(401).json({ msg: "Unauthorized" }); return; } const username = context.user?.getFirstProviderUserId(); res.json({ msg: `Hello, ${username}!` }); };API 参考:完整字段说明
api声明的完整形态如下:
api fooBar { fn: import { fooBar } from "@src/apis", httpRoute: (GET, "/foo/bar"), entities: [Task], auth: true, middlewareConfigFn: import { apiMiddleware } from "@src/apis" }各字段说明:
| 字段 | 是否必填 | 说明 |
|---|---|---|
fn: ExtImport | 必填 | API 的 Node.js 实现的导入语句。 |
httpRoute: (HttpMethod, string) | 必填 | HTTP(方法, 路径)二元组。方法可以是ALL、GET、POST、PUT或DELETE;路径是 Express 路径字符串(支持:param路径参数)。 |
entities: [Entity] | 可选 | 希望在 API 内部使用的 Entity 列表(详见上文 在 API 中使用 Entities)。 |
auth: bool | 可选 | 若应用启用了认证,默认值为true,会提供context.user;若不想解析Authorization头中的 JWT,请设为false。 |
middlewareConfigFn: ExtImport | 可选 | 为该 API 指定的 Express 中间件配置函数导入语句(详见 Middleware Configuration)。 |
补充说明:
- 上述
HttpMethod与(HttpMethod, string)的类型定义可在 spec 包源码中确认:waspc/data/packages/spec/src/spec/publicApi/waspSpec.ts 中定义了export type HttpMethod = "ALL" | "GET" | "POST" | "PUT" | "DELETE"。 - 在 waspc/data/packages/spec/src/spec/publicApi/constructors.ts 中,
api(method, path, fn, config?)构造函数接收middlewareConfigFn、entities、auth三个可选配置;apiNamespace(path, config)则为路径前缀批量应用共享中间件(如原始 body 解析、CORS),适合为一组相关端点统一处理。
实战案例:kitchen-sink 中的完整 API 组合
examples/kitchen-sink示例应用是理解 API 能力边界的最佳参考。它的 Spec 文件 examples/kitchen-sink/src/features/apis/apis.wasp.ts 同时展示了api、apiNamespace以及认证开关的组合用法:
export const apisSpec: Spec = [ route("ApisRoute", "/apis", page(ApisPage)), api("ALL", "/foo/bar", fooBar, { middlewareConfigFn: fooBarMiddlewareFn, entities: ["Task"], }), apiNamespace("/bar", { middlewareConfigFn: barNamespaceMiddlewareFn, }), api("GET", "/bar/baz", barBaz, { auth: false, entities: ["Task"] }), api("POST", "/webhook/callback", webhookCallback, { middlewareConfigFn: webhookCallbackMiddlewareFn, auth: false, }), ];对应的实现 examples/kitchen-sink/src/features/apis/apis.ts 展示了三种典型中间件操作:
- 追加自定义中间件:
fooBarMiddlewareFn通过middlewareConfig.set("custom.route", customMiddleware)在已有配置上追加一个打印日志的中间件; - 替换默认中间件:
webhookCallbackMiddlewareFn使用middlewareConfig.delete("express.json")删掉 JSON 解析器,再middlewareConfig.set("express.raw", express.raw({ type: "*/*" }))换成原始 body 解析——这是 Webhook 接收二进制/任意 content-type 载荷的常见需求; - 批量覆盖命名空间:
barNamespaceMiddlewareFn为/bar前缀下的所有 API 配置统一的自定义中间件。
值得注意的细节是:/foo/bar未显式设置auth,因此默认启用认证(返回 401 分支);而/bar/baz与/webhook/callback都显式设置了auth: false——前者是公开问候接口,后者是外部 Webhook 回调(无法携带你的 JWT)。这种"默认受保护、显式开放"的设计,与文档中"认证启用时auth默认为true"的规则完全一致,可以作为你在生产项目中的参考范式。
总结
Wasp 的自定义 API 端点功能在 Operations 之外提供了一条"完全掌控 HTTP 层"的通道:
- 两步创建:Wasp 文件里声明
api+src里实现 Node.js 函数,即可获得一个标准的 Express 路由; - 全类型安全:TypeScript 下
FooBar<Params, Response>泛型让路径参数与响应体全程受编译器保护; - 客户端开箱即用:
wasp/client/api的 Axios 包装器自动携带认证信息,一行api.get(...)即可调用; - 中间件自由:
middlewareConfigFn与apiNamespace让你像操作原生 Express 一样增删改查中间件(CORS、raw body、日志等); - 数据库零配置接入:
entities声明后context.entities.X直接暴露 Prisma CRUD API; - 认证可控:
auth: true(默认)解析 JWT 注入context.user,auth: false则完全跳过 JWT 解析,适配公开接口与 Webhook 场景。
如果你的需求是"需要特定 URL 方法/路径、特定响应、或对接外部系统",自定义 API 就是 Wasp 为这类场景预留的正确入口;而常规的前端数据读写,仍应优先使用带自动客户端辅助函数的 Operations。
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考