ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Wasp 中间件配置实战:Express 全局、api 级与路径级三级定制机制解析

2026/9/14 7:56:22 拓冰建站 浏览量
Wasp 中间件配置实战:Express 全局、api 级与路径级三级定制机制解析 Wasp 中间件配置实战Express 全局、api 级与路径级三级定制机制解析【免费下载链接】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 为每个应用的 Express 服务器内置了一组开箱即用的中间件Helmet、CORS、Morgan、express.json 等并开放了三个层级的定制入口全局server.middlewareConfigFn、单条api路由级与apiNamespace路径级。读完本文你将理解这组默认中间件的构成与必要性掌握MiddlewareConfig/MiddlewareConfigFn的类型模型并能针对跨域域名扩展、Webhook 原始请求体解析、路径前缀统一鉴权等真实场景完成中间件的增、删、改且不会破坏 Wasp Operationsquery/action的底层依赖。一、默认全局中间件Wasp 替你装了什么Wasp 的 Express 服务器默认装载以下中间件它们按固定顺序生效中间件作用必要性Helmet通过设置一系列安全相关的 HTTP 响应头来加固 Express 应用良好的安全起点但并非万灵药CORS提供可配置的跨域中间件前端与后端通信所必需缺少它前端请求会被浏览器拦截MorganHTTP 请求日志记录器开发期请求可观测性express.json解析 JSON 请求体结果挂在req.bodyOperationsquery/action正常工作的必要条件express.urlencoded仅解析Content-Type匹配的 urlencoded 请求体表单类请求支持cookieParser解析 Cookie 头填充req.cookies对象会话/身份识别依赖这些定义的源码出处是 Wasp 生成代码中的globalMiddleware.ts构建模板见 wasp-app-runner/src/middleware/globalMiddleware.ts其核心是一张Mapkey 是中间件标识符、value 是express.RequestHandlerconst defaultGlobalMiddleware new Map([ [helmet, helmet()], [cors, cors({ origin: config.allowedCORSOrigins })], [logger, logger(dev)], [express.json, express.json()], [express.urlencoded, express.urlencoded({ extended: false })], [cookieParser, cookieParser()] ])TypeScript 版本中这张表带有完整类型这也是后续定制函数的签名基础export type MiddlewareConfig Mapstring, express.RequestHandler // 下面示例中的 middlewareConfigFn 就是这个签名 export type MiddlewareConfigFn (middlewareConfig: MiddlewareConfig) MiddlewareConfig const defaultGlobalMiddleware: MiddlewareConfig new Map([ [helmet, helmet()], [cors, cors({ origin: config.allowedCORSOrigins })], [logger, logger(dev)], [express.json, express.json()], [express.urlencoded, express.urlencoded({ extended: false })], [cookieParser, cookieParser()] ])注意几个实现细节CORS 的origin直接取自config.allowedCORSOrigins这意味着你改 CORS 域名时Wasp 配置里的allowedCORSOrigins才是“原始值”定制函数拿到的是基于它构建好的实例express.urlencoded显式使用extended: false日志中间件的 key 是logger而非morganset覆盖时务必用对 key。二、三个定制层级全局、api 级、路径级Wasp 提供三个互不干扰的定制位置选择依据是“影响面”全局global改动默认作用于所有 Operationsquery和action以及所有api。适合例如给 CORS 增加多个可信任域名。需要极其谨慎——它会波及每一个端点拿不准时优先用下面两个方案。per-api只覆盖某一条api路由例如POST /webhook/callback。适合针对某个回调禁用 JSON 解析、改用原始请求体等。per-pathapiNamespace对某个公共路径下的所有方法生效。适合“复杂 CORS 预检”这类需要同时覆盖OPTIONS与GET的场景或想给一组api路由统一挂某个中间件。层级关系可以理解为先应用全局中间件表再叠加路径级挂载在路由前缀上最后叠加单 api 级挂载在具体方法路由上越靠后、作用域越小。2.1 定制全局中间件server.middlewareConfigFn在main.wasp的server字段中声明middlewareConfigFnapp todoApp { // ... server: { setupFn: import setup from src/serverSetup, middlewareConfigFn: import { serverMiddlewareFn } from src/serverSetup }, }然后在src/serverSetup.ts中实现该函数。文档给出的经典用例是给 CORS 追加多个可信任域名import cors from cors import { config, type MiddlewareConfigFn } from wasp/server export const serverMiddlewareFn: MiddlewareConfigFn (middlewareConfig) { // 示例为 CORS 增加额外域名。 middlewareConfig.set(cors, cors({ origin: [config.frontendUrl, https://example1.com, https://example2.com] })) return middlewareConfig }仓库中的 kitchen-sink 示例 正是这一模式的真实落地它通过扩展config.allowedCORSOrigins来追加本地开发域名比硬编码域名列表更贴合 Wasp 自身的配置模型// examples/kitchen-sink/src/serverSetup.ts第 48-55 行 export const serverMiddlewareFn: MiddlewareConfigFn (middlewareConfig) { // Example of adding an extra domain to CORS. middlewareConfig.set( cors, cors({ origin: [...config.allowedCORSOrigins, http://127.0.0.1:3000] }), ); return middlewareConfig; };两者的差别值得注意官方文档示例是把config.frontendUrl与固定域名拼成新数组kitchen-sink 则是保留config.allowedCORSOrigins的全部来源再追加。如果你的应用存在多环境部署本地、staging、生产各自有不同的前端域名推荐后者的写法避免在代码里散落环境相关的字面量。2.2 定制单条 api 的中间件api.middlewareConfigFn以 Webhook 回调为例这类接口如支付服务商回调通常需要原始请求体做签名校验express.json会把 body 先解析掉因此需要把它换成express.raw// ... api webhookCallback { fn: import { webhookCallback } from src/apis, middlewareConfigFn: import { webhookCallbackMiddlewareFn } from src/apis, httpRoute: (POST, /webhook/callback), auth: false }import express from express import { type WebhookCallback } from wasp/server/api import { type MiddlewareConfigFn } from wasp/server export const webhookCallback: WebhookCallback (req, res, _context) { res.json({ msg: req.body.length }) } export const webhookCallbackMiddlewareFn: MiddlewareConfigFn (middlewareConfig) { console.log(webhookCallbackMiddlewareFn: Swap express.json for express.raw) middlewareConfig.delete(express.json) middlewareConfig.set(express.raw, express.raw({ type: */* })) return middlewareConfig }实现原理per-api 的中间件是按 HTTP 方法粒度安装的底层等价于router.post(/webhook/callback, webhookCallbackMiddleware, ...)这带来两个推论。其一deleteset的组合是“替换”语义——必须先把全局表里的express.json删掉否则 JSON 解析会先于express.raw执行req.body已经是解析后的对象kitchen-sink 的回调示例正是依赖req.body.length在express.raw下它返回的是 Buffer 长度这也侧面印证了 body 确实按原始字节进入了处理函数。其二因为挂载在具体方法上OPTIONS预检请求不会命中这条 api 的中间件链——这正是第三层级存在的原因。2.3 定制路径级中间件apiNamespace.middlewareConfigFn当需要对某一路径前缀下的所有方法统一加逻辑例如复杂 CORS 场景下同时覆盖OPTIONS和GET或给一组路由统一挂审计日志把middlewareConfigFn定义在apiNamespace上// ... apiNamespace fooBar { middlewareConfigFn: import { fooBarNamespaceMiddlewareFn } from src/apis, path: /foo/bar }import express from express import { type MiddlewareConfigFn } from wasp/server export const fooBarNamespaceMiddlewareFn: MiddlewareConfigFn (middlewareConfig) { const customMiddleware: express.RequestHandler (_req, _res, next) { console.log(fooBarNamespaceMiddlewareFn: custom middleware) next() } middlewareConfig.set(custom.middleware, customMiddleware) return middlewareConfig }实现原理上它挂载在路由器的路径前缀上底层等价于router.use(/foo/bar, fooBarNamespaceMiddleware)与 per-api 的router.post(...)不同router.use对前缀下所有方法生效因此天然覆盖OPTIONS预检请求——这是处理“复杂 CORS 请求”携带自定义头、触发预检的推荐位置。三、实操要点与注意事项Map的顺序即执行顺序。MiddlewareConfig是Mapstring, express.RequestHandler插入顺序决定装载顺序。用set覆盖已有 key 会保持原位置、替换实现新增 key 则追加到末尾。想让自定义中间件跑在解析器之前例如按Content-Type提前拦截需要把原条目删除后按期望位置重新set。覆盖 key 要用准。默认表的 key 为helmet、cors、logger、express.json、express.urlencoded、cookieParser写错 key 不会报错只会悄悄追加一个排在所有解析器之后的新中间件行为与预期不符。改全局中间件是高危操作。它会作用于每一个query/action和api例如误删express.json会导致所有 Operations 的请求体解析失效这是 Operations 正常工作的必要条件。拿不准时把改动收敛到 per-api 或 per-path 层级。CORS 定制建议基于config.allowedCORSOrigins扩展而非硬编码这样 Wasp 配置里声明的前端地址不会被丢掉参考 examples/kitchen-sink/src/serverSetup.ts。Webhook 场景遵循“先删后换”middlewareConfig.delete(express.json)之后再set(express.raw, express.raw({ type: */* }))并注意该 api 应显式auth: false以免被鉴权中间件拦截。四、小结Wasp 的中间件体系可以概括为“一张默认表 三个钩子”默认globalMiddleware表保证 Operations、CORS、日志与安全头等开箱即用server.middlewareConfigFn提供全局重写能力如扩展 CORS 域名api.middlewareConfigFn提供按方法粒度的精确替换如 Webhook 换用express.rawapiNamespace.middlewareConfigFn则提供router.use语义的路径级覆盖如统一处理 OPTIONS 预检。三者共享同一套MiddlewareConfig Mapstring, express.RequestHandler类型契约结合 wasp-app-runner/src/middleware/globalMiddleware.ts 的默认实现与 kitchen-sink 示例 的真实用法可以按需组合出既保持 Wasp 默认行为、又满足特殊路由需求的中间件栈。【免费下载链接】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),仅供参考