Wasp 自定义 HTTP API 端点完全指南:从 api 声明到 Express 路由的实战详解
【免费下载链接】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)完成前后端通信,但当你需要精确控制 URL 的 method/path、自定义响应格式,或对接 Webhook、第三方回调等特殊场景时,api声明是更合适的选择。本文以 Wasp v0.18 的官方文档为主体,结合仓库中 Wasp 编译器的实际源码与生成模板,系统讲解自定义 HTTP API 端点的声明、实现、调用、CORS 配置与 Entity 注入,帮助你写出可复制、可运行且类型安全的自定义路由。
Operations 之外的选择:为什么需要api
在 Wasp 中,默认的前后端交互机制是 Operations(即query与action)。但如果你需要特定的 URL method/path(例如POST /something/special),或者需要特定的响应结构,Operations 可能并不合适——此时就可以使用api。
api的作用是把一个 JavaScript/TypeScript 函数绑定到某个具体端点(endpoint)上。它与 Operations 有两点关键区别:
- 它是纯粹的 HTTP 端点,没有客户端辅助工具(如
useQuery); - 它不强制遵循 Operations 的调用约定,你可以完全控制请求与响应。
好消息是,api的用法与 Express 路由非常相似,学习成本很低。创建 Wasp API 只需要两个步骤:
- 在 Wasp 文件中使用
api声明该 API; - 定义该 API 的 NodeJS 实现函数。
完成这两步后,你就可以从客户端代码(通过 Wasp 提供的 Axios 包装器)或从外部世界调用这个 API 了。
第一步:在 Wasp 文件中声明 API
在项目根目录的main.wasp中,使用api声明即可定义一个 API:
// ... api fooBar { // APIs and their implementations don't need to (but can) have the same name. fn: import { fooBar } from "@src/apis", httpRoute: (GET, "/foo/bar") }这里的两个核心字段是:
fn:指向 API 的 NodeJS 实现(通过import语法从@src/apis引入);httpRoute:(HttpMethod, path)形式的二元组,例如(GET, "/foo/bar")。
注意:api声明的名字与其实现函数的名字不必相同(当然也可以相同)。上例中声明名为fooBar,实现导入名也叫fooBar,这只是习惯使然。
从编译器源码看,api声明的完整数据结构定义在 waspc/src/Wasp/AppSpec/Api.hs:
data Api = Api { fn :: ExtImport, middlewareConfigFn :: Maybe ExtImport, entities :: Maybe [Ref Entity], httpRoute :: (HttpMethod, String), -- (method, path), exe: (GET, "/foo/bar") auth :: Maybe Bool }其中HttpMethod被限定为以下五种取值(见 Api.hs):
data HttpMethod = ALL | GET | POST | PUT | DELETE也就是说,httpRoute的第一个元素只能是ALL、GET、POST、PUT、DELETE之一,第二个元素是 Express 风格的路径字符串。
第二步:定义 API 的 NodeJS 实现
在声明 API 之后,需要实现它。实现是一个接收三个参数的 NodeJS 函数:
req:Express 的 Request 对象;res:Express 的 Response 对象;context:由 Wasp 注入的附加上下文对象,包含用户会话信息(context.user)以及实体信息(context.entities)。本节示例暂时不用context,关于实体的用法见下文「在 API 中使用 Entity」。
import type { FooBar } from "wasp/server/api"; export const fooBar: FooBar = (req, res, context) => { res.set("Access-Control-Allow-Origin", "*"); // Example of modifying headers to override Wasp default CORS middleware. res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` }); };这段代码演示了两个实用技巧:
- 通过
res.set("Access-Control-Allow-Origin", "*")直接修改响应头,以覆盖 Wasp 默认的 CORS 中间件行为; - 通过
context.user判断当前请求是否来自已注册用户,从而实现"已注册用户/陌生人"的差异化响应。
对于 TypeScript 项目,FooBar类型是Wasp 根据api声明自动生成的。要确保类型在编写实现时可用,请先把api声明写入.wasp文件,并保持wasp start命令运行——Wasp 编译器会在后台持续生成并更新这些类型。
从源码看,这些类型确实是由编译器按声明动态生成的:在 waspc/src/Wasp/Generator/SdkGenerator/ServerApiG.hs 中,编译器会遍历所有api声明(getApis spec),将每个 API 的名字转为typeName(首字母大写),并依据其usesAuth与entities字段决定生成的类型签名;对应的类型定义模板位于 waspc/data/Generator/templates/sdk/wasp/server/api/index.ts,其中FooBar这类类型默认接受P(路径参数)、ResBody、ReqBody、ReqQuery、Locals五个泛型参数,且使用认证时会落到AuthenticatedApi<...>,否则落到Api<...>。
为 API 提供额外的类型信息
假设你想创建一个GET路由,接收一个 email 地址作为路径参数,并返回"生命、宇宙以及一切终极问题的答案"(42)。在 TypeScript 下可以这样实现全类型安全的自定义 API。
首先在 Wasp 中声明带路径参数的路由,并声明要用到的 Entity:
api fooBar { fn: import { fooBar } from "@src/apis", entities: [Task], httpRoute: (GET, "/foo/bar/:email") }然后为FooBar类型提供两个泛型参数——params(路径参数)与response(响应体)类型:
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 }); };此时req.params.email会被推断为string,res.json({ answer: 42 })也会被校验是否满足{ answer: number }的响应类型——这就是泛型参数带来的端到端类型安全。
调用 API
API 声明并实现完成后,可以同时从外部与客户端两种途径调用。
从外部调用
外部调用非常简单:直接用你声明的 method 和 path 请求该端点即可。
例如,假设你的应用运行在https://example.com,那么可以发起一个GET请求到https://example.com/foo/bar——无论是浏览器地址栏、Postman、curl,还是其他 Web 服务,都可以直接调用:
curl https://example.com/foo/bar从客户端调用
在客户端(包括需要携带认证的场景)调用时,可以导入 Wasp 提供的Axios 包装器wasp/client/api,它会预先配置好 API 的基础 URL、认证信息与错误处理:
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 <></>; };由于该包装器已预配置认证信息,即使你的 API 开启了auth: true,客户端调用时也会自动携带登录凭证(JWT)。
确保 CORS 正常工作
一个重要的注意事项:API 被设计得尽可能灵活,因此不会像 Operations 那样自动使用默认中间件。这意味着要在客户端侧正常使用这些 API,必须确保 CORS(跨域资源共享)已开启。
实现方式是在 Wasp 文件中为 API 定义自定义中间件。其中apiNamespace是一种简单的声明,用于把某个middlewareConfigFn应用到指定路径下所有 API:
apiNamespace fooBar { middlewareConfigFn: import { fooBarNamespaceMiddlewareFn } from "@src/apis", path: "/foo" }然后在实现文件中(此处直接返回默认配置):
import type { MiddlewareConfigFn } from "wasp/server"; export const apiMiddleware: MiddlewareConfigFn = (config) => { return config; };返回默认配置意味着/foo路径下所有 API 都会启用 Wasp 默认的 CORS 中间件,从而允许前端跨域调用。
apiNamespace在编译器中的数据结构定义于 waspc/src/Wasp/AppSpec/ApiNamespace.hs,只有两个字段:middlewareConfigFn(必填的中间件配置函数导入)与path(路径前缀)。
至于中间件的生成逻辑,可以看 waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs 以及路由模板 waspc/data/Generator/templates/server/src/routes/apis/index.ts。模板中,命名空间中间件被安装在路由层:
router.use('/foo', globalMiddlewareConfigForExpress(fooBarNamespaceMiddlewareFn))而单个 API 的中间件则按 method 粒度挂载在对应路由上:
router.get('/foo/bar', fooBarMiddleware, defineHandler(...))关于中间件配置的更多细节(全局中间件、per-api 中间件、per-path 中间件的三种定制位置,以及 Helmet、CORS、Morgan、express.json、express.urlencoded、cookieParser等默认中间件的完整定义),请参阅 Middleware Configuration。其中特别提到一个典型场景:Webhook 回调需要接收原始请求体时,可以在middlewareConfigFn中delete('express.json')并替换为express.raw({ type: '*/*' })。
在 API 中使用 Entity
很多情况下,API 中要用到的资源就是 Entity。在 Wasp 中把 Entity 加入api声明的entities字段即可:
api fooBar { fn: import { fooBar } from "@src/apis", entities: [Task], httpRoute: (GET, "/foo/bar") }Wasp 会把声明的 Entity 注入到 API 的context参数中,从而让你在实现里直接使用该 Entity 的 Prisma API:
import type { FooBar } from "wasp/server/api"; export const fooBar: FooBar = async (req, res, context) => { res.json({ count: await context.entities.Task.count() }); };其中context.entities.Task暴露的正是prisma.task,即 Prisma Client 的 CRUD API。因此你可以在 API 中执行findMany、create、update、count等所有 Prisma 操作,并且完全类型安全。
这一注入机制在生成模板 waspc/data/Generator/templates/server/src/routes/apis/index.ts 中体现得很直观:编译器会为每个 API 生成context对象,把声明的实体映射为prisma.<prismaIdentifier>:
const context = { user: makeAuthUserIfPossible(req.user), entities: { Task: prisma.task, }, } return fooBar(req, res, context)也就是说,你写的context.entities.Task在编译后真实指向prisma.task的完整 CRUD 接口。
API Reference:字段完整说明
下面是一个包含全部可选字段的完整api声明:
api fooBar { fn: import { fooBar } from "@src/apis", httpRoute: (GET, "/foo/bar"), entities: [Task], auth: true, middlewareConfigFn: import { apiMiddleware } from "@src/apis" }api声明支持以下字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
fn | ExtImport | ✅ | API 的 NodeJS 实现函数的导入语句。 |
httpRoute | (HttpMethod, string) | ✅ | HTTP 的(方法, 路径)二元组。方法只能是ALL、GET、POST、PUT、DELETE之一;路径是 Express 风格的路径字符串。 |
entities | [Entity] | ❌ | 希望在 API 内部使用的 Entity 列表,会被注入到context.entities中(详见上文「在 API 中使用 Entity」)。 |
auth | bool | ❌ | 如果应用开启了认证,此字段默认值为true,会向 API 提供context.user对象。如果你不希望解析 Authorization Header 中的 JWT(例如公开的 Webhook 回调),应显式设为false。 |
middlewareConfigFn | ExtImport | ❌ | 指向该 API 的 Express 中间件配置函数的导入语句(详见 Middleware Configuration)。 |
关于auth的默认值行为,源码中有明确的对应逻辑:在 ApiRoutesG.hs 中,isAuthEnabledForApi的实现是fromMaybe (isAuthEnabled spec) (Api.auth api)——即当某个 API 没有显式设置auth时,默认沿用整个应用的认证开关状态。若应用开启认证,则该 API 默认启用auth: true,生成的路由会带上[auth, ...fooBarMiddleware]认证中间件链,并在context.user中注入通过makeAuthUserIfPossible(req.user)解析出的用户数据(见 路由模板)。
小结
自定义 HTTP API 端点是 Wasp 在 Operations 之外为"非常规"接口需求提供的灵活出口。回顾全文要点:
- 两个步骤:在
main.wasp中用api声明(含fn与httpRoute),再实现接收(req, res, context)三参数的 NodeJS 函数; - 类型安全:
FooBar等类型由 Wasp 按声明自动生成,可用泛型标注路径参数与响应类型,实现端到端类型校验; - 灵活调用:外部可直接
curl,客户端通过wasp/client/api的 Axios 包装器调用,自动携带认证信息; - CORS 兜底:API 不默认附带 Operations 的中间件,需借助
apiNamespace/middlewareConfigFn确保跨域可用; - 数据访问:通过
entities字段将 Prisma CRUD API 注入context.entities,在自定义路由中直接读写数据库。
掌握以上要点后,无论是 Webhook 接收、第三方回调、自定义响应格式,还是需要精确控制 method/path 的 REST 风格接口,都可以用 Waspapi优雅地实现。
【免费下载链接】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),仅供参考