news 2026/9/16 1:08:43

Wasp 自定义 HTTP API 端点完全指南:从 api 声明到 Express 路由的实战详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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/wasp

Wasp 默认通过 Operations(Query/Action)完成前后端通信,但当你需要精确控制 URL 的 method/path、自定义响应格式,或对接 Webhook、第三方回调等特殊场景时,api声明是更合适的选择。本文以 Wasp v0.18 的官方文档为主体,结合仓库中 Wasp 编译器的实际源码与生成模板,系统讲解自定义 HTTP API 端点的声明、实现、调用、CORS 配置与 Entity 注入,帮助你写出可复制、可运行且类型安全的自定义路由。

Operations 之外的选择:为什么需要api

在 Wasp 中,默认的前后端交互机制是 Operations(即queryaction)。但如果你需要特定的 URL method/path(例如POST /something/special),或者需要特定的响应结构,Operations 可能并不合适——此时就可以使用api

api的作用是把一个 JavaScript/TypeScript 函数绑定到某个具体端点(endpoint)上。它与 Operations 有两点关键区别:

  • 它是纯粹的 HTTP 端点,没有客户端辅助工具(如useQuery);
  • 它不强制遵循 Operations 的调用约定,你可以完全控制请求与响应。

好消息是,api的用法与 Express 路由非常相似,学习成本很低。创建 Wasp API 只需要两个步骤:

  1. 在 Wasp 文件中使用api声明该 API;
  2. 定义该 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的第一个元素只能是ALLGETPOSTPUTDELETE之一,第二个元素是 Express 风格的路径字符串。

第二步:定义 API 的 NodeJS 实现

在声明 API 之后,需要实现它。实现是一个接收三个参数的 NodeJS 函数:

  1. req:Express 的 Request 对象;
  2. res:Express 的 Response 对象;
  3. 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(首字母大写),并依据其usesAuthentities字段决定生成的类型签名;对应的类型定义模板位于 waspc/data/Generator/templates/sdk/wasp/server/api/index.ts,其中FooBar这类类型默认接受P(路径参数)、ResBodyReqBodyReqQueryLocals五个泛型参数,且使用认证时会落到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会被推断为stringres.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.jsonexpress.urlencodedcookieParser等默认中间件的完整定义),请参阅 Middleware Configuration。其中特别提到一个典型场景:Webhook 回调需要接收原始请求体时,可以在middlewareConfigFndelete('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 中执行findManycreateupdatecount等所有 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声明支持以下字段:

字段类型必填说明
fnExtImportAPI 的 NodeJS 实现函数的导入语句。
httpRoute(HttpMethod, string)HTTP 的(方法, 路径)二元组。方法只能是ALLGETPOSTPUTDELETE之一;路径是 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声明(含fnhttpRoute),再实现接收(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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 1:07:54

STK Commu模块链路参数计算实战:从链路预算到低轨卫星仿真

STK的Commu模块我用了不少年头&#xff0c;从最早的STK 8到现在的新版本&#xff0c;光是给各类低轨星座做链路预算就不知道跑了多少轮。这个系列前两篇聊了Commu模块的基础操作和收发机建模&#xff0c;这次专门把链路参数计算这块摊开讲。很多刚接触STK的朋友最容易卡在这一步…

作者头像 李华
网站建设 2026/9/16 1:07:52

嵌入式C实现DMX512微秒级时序驱动

简介&#xff1a;本资源是一份面向嵌入式开发初学者与灯光控制项目实践者的DMX512协议发送端C语言实现代码&#xff0c;聚焦于舞台灯光、智能照明等实时控制场景中核心通信功能的落地。压缩包仅含1个关键文件——dmx512_send_code.c&#xff08;725B&#xff09;&#xff0c;完…

作者头像 李华
网站建设 2026/9/16 1:07:39

OpenClaw 配 TaoToken:Win10 整合包从解压到任务测试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 1:05:52

离散小波变换结合一维卷积神经网络的心电自动分类实践

简介&#xff1a;基于离散小波变换与一维卷积神经网络的心电自动分类Matlab实现&#xff0c;面向生物医学工程、电子信息、计算机等专业需要完成课程设计、期末大作业或毕业设计的本硕群体。资源共10个文件&#xff0c;以5个.m源码、1个.mat心电数据、1个.py分类脚本及readme/t…

作者头像 李华
网站建设 2026/9/16 1:03:21

集成学习完全指南:从Bagging到XGBoost、LightGBM与CatBoost

做竞赛、搞建模或者调模型调到头秃的朋友&#xff0c;一定绕不开集成学习。我在实际项目里试过单一模型死磕到极致&#xff0c;最后评分纹丝不动&#xff0c;反而是一顿 bagging、boosting 组合拳下去&#xff0c;线上指标直接涨了一截。这篇总结想把我对集成学习从原理到代码的…

作者头像 李华
网站建设 2026/9/16 1:02:18

管住Cursor的7条铁律:让AI编程不再失控

说实话&#xff0c;我第一次用 Cursor 的时候是有点上头的。AI 补全快得离谱&#xff0c;Tab 一按就是半屏代码&#xff0c;聊几句就能把一个模块生成出来&#xff0c;整个人感觉像换了台法拉利。但用了大概两周之后&#xff0c;我被迫面对一个现实&#xff1a;我的项目开始失控…

作者头像 李华