Wasp 自定义 HTTP API 端点完全指南:从 api 声明到 Express 路由的实战详解
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/waspWasp 默认通过 OperationsQuery/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 dont 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.hsdata 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.hsdata HttpMethod ALL | GET | POST | PUT | DELETE也就是说httpRoute的第一个元素只能是ALL、GET、POST、PUT、DELETE之一第二个元素是 Express 风格的路径字符串。第二步定义 API 的 NodeJS 实现在声明 API 之后需要实现它。实现是一个接收三个参数的 NodeJS 函数reqExpress 的 Request 对象resExpress 的 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 中声明带路径参数的路由并声明要用到的 Entityapi 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会被推断为stringres.json({ answer: 42 })也会被校验是否满足{ answer: number }的响应类型——这就是泛型参数带来的端到端类型安全。调用 APIAPI 声明并实现完成后可以同时从外部与客户端两种途径调用。从外部调用外部调用非常简单直接用你声明的 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应用到指定路径下所有 APIapiNamespace 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 APIimport 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.prismaIdentifierconst 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声明支持以下字段字段类型必填说明fnExtImport✅API 的 NodeJS 实现函数的导入语句。httpRoute(HttpMethod, string)✅HTTP 的方法, 路径二元组。方法只能是ALL、GET、POST、PUT、DELETE之一路径是 Express 风格的路径字符串。entities[Entity]❌希望在 API 内部使用的 Entity 列表会被注入到context.entities中详见上文「在 API 中使用 Entity」。authbool❌如果应用开启了认证此字段默认值为true会向 API 提供context.user对象。如果你不希望解析 Authorization Header 中的 JWT例如公开的 Webhook 回调应显式设为false。middlewareConfigFnExtImport❌指向该 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),仅供参考