ARTICLE DETAIL

建站实战干货

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

Wasp 文件上传实战:基于 Multer 与 API 中间件实现完整上传链路

2026/9/13 17:17:30 拓冰建站 浏览量
Wasp 文件上传实战:基于 Multer 与 API 中间件实现完整上传链路 Wasp 文件上传实战基于 Multer 与 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/wasp导读本文讲解如何在 Wasp 应用中基于 MulterExpress 生态最流行的 multipart/form-data 处理中间件实现文件上传。文章以 Wasp 的apiNamespace/api声明式路由为核心覆盖中间件挂载、上传接口、前端表单、大小限制、类型过滤、多文件上传等完整链路并结合本仓库的生成器模板与快照测试源码深入剖析 Wasp 服务端路由与中间件机制的实际工作原理。读完本文你将能独立在 Wasp 项目中搭建一套可配置、可扩展的文件上传服务。适用版本提示本文关联的官方指南以 Wasp 0.24、multer 2.1.1 版本为基准编写见原文档顶部版本注记。1. 为什么在 Wasp 中上传文件要借助 MulterWasp 的声明式规范main.wasp.ts内置了路由route、页面page、查询query、操作action、APIapi等抽象但文件上传这类需要处理multipart/form-data请求体的场景Wasp默认并不内置对应的解析中间件。默认的全局中间件只包含 JSON 与 urlencoded 解析器。从 服务端中间件模板 可以看到 Wasp 默认挂载的中间件清单const defaultGlobalMiddlewareConfig: MiddlewareConfig new Map([ [helmet, helmet()], [cors, cors({ origin: config.allowedCORSOrigins })], [logger, logger(dev)], [express.json, express.json()], [express.urlencoded, express.urlencoded()], [cookieParser, cookieParser()] ])其中express.json无法解析二进制文件体因此上传文件的标准做法是通过 Wasp 的中间件配置钩子MiddlewareConfigFn把 Multer 挂到指定的 API 路由上让 Multer 接管该路由的请求体解析。Wasp 官方指南即本文所依据的 file-upload.md正是采用这一方案。2. 中间件机制速览apiNamespace / api / MiddlewareConfigFn在动手之前先理解 Wasp 提供的三个关键声明它们在 Wasp 规范包的公共 API 中定义api(method, path, handler, options)声明一个自定义 HTTP API 路由。options支持middlewareConfigFn路由级中间件定制、entities注入 Prisma 实体、auth是否启用鉴权默认继承全局设置。apiNamespace(path, options)为某个路径前缀下的所有路由批量挂载中间件是文件上传场景的核心。MiddlewareConfigFn(middlewareConfig: Mapstring, RequestHandler) Mapstring, RequestHandler接收一份中间件 Map返回修改后的 Map。从 Haskell 侧的应用规范定义可以印证这些字段ApiNamespace由middlewareConfigFn与path两个字段构成见 ApiNamespace.hs而Api则包含middlewareConfigFn、entities、auth三个可选字段见 Api.hs。在 kitchen-sink 示例的快照中可以看到三者组合使用的真实样例apis.wasp.ts 快照apiNamespace(/bar, { middlewareConfigFn: barNamespaceMiddlewareFn, }), api(GET, /bar/baz, barBaz, { auth: false, entities: [Task] }), api(POST, /webhook/callback, webhookCallback, { middlewareConfigFn: webhookCallbackMiddlewareFn, auth: false, }),对应实现里展示了MiddlewareConfigFn的典型用法——set添加/替换中间件、delete移除中间件apis.ts 快照middlewareConfig.set(custom.route, customMiddleware); // ... middlewareConfig.delete(express.json); middlewareConfig.set(express.raw, express.raw({ type: */* }));这些声明最终由生成器ApiRoutesG.hs编译为 Express 路由代码见 ApiRoutesG.hs输出到src/routes/apis/index.ts。3. 完整实战四步实现文件上传3.1 安装 Multer在 Wasp 项目根目录安装运行时依赖与类型声明npm install multer npm install --save-dev types/multer3.2 在 main.wasp.ts 中声明上传 API在main.wasp.ts的spec数组中通过apiNamespace为/api/upload前缀挂载中间件配置函数再通过api声明POST /api/upload路由指向上传处理器import { api, apiNamespace, app, page, route } from wasp.sh/spec import { configureFileUploadMiddleware, uploadFile } from ./src/apis with { type: ref } import { MainPage } from ./src/MainPage with { type: ref } export default app({ // ... spec: [ route(RootRoute, /, page(MainPage)), apiNamespace(/api/upload, { middlewareConfigFn: configureFileUploadMiddleware }), api(POST, /api/upload, uploadFile), ], })注意with { type: ref }是 Wasp 规范中引用src/目录源码文件的固定语法不可省略。从生成模板apis/index.ts 模板可以看到apiNamespace声明的中间件最终会通过router.use(namespacePath, globalMiddlewareConfigForExpress(fn))挂载到该路径前缀下而单个api路由的中间件则通过routermethod逐个应用若启用鉴权auth中间件会被插入到自定义中间件之前。这意味着configureFileUploadMiddleware会对/api/upload前缀下的所有请求生效。3.3 创建中间件配置与上传处理器新建src/apis.ts或src/apis/index.ts包含两部分import type { MiddlewareConfigFn } from wasp/server; import type { UploadFile } from wasp/server/api; import multer from multer; const upload multer({ dest: uploads/ }); export const configureFileUploadMiddleware: MiddlewareConfigFn (config) { config.set(multer, upload.single(file)); return config; }; export const uploadFile: UploadFile (req, res) { console.log(req.body); console.log(req.file); const file req.file!; return res.json({ fileExists: !!file, }); };关键点说明multer({ dest: uploads/ })创建 Multer 实例dest指定文件落盘目录相对服务端运行目录upload.single(file)表示只接收字段名为file的单个文件MiddlewareConfigFn的返回值类型必须是中间件Map用config.set(multer, ...)将 Multer 中间件加入键名multer可自定义但要保证不与现有默认中间件键冲突UploadFile是 Wasp 生成的处理器类型其签名接收(req, res, context)context中会注入声明的entities与user详见生成模板 apis/index.ts 模板处理完成后返回 JSON 结果即可req.file中包含文件名、MIME 类型、大小、存储路径等信息。3.4 创建前端上传表单前端通过 Wasp 提供的wasp/client/api客户端基于 fetch 封装自动携带认证信息与正确 baseURL以FormData发起multipart/form-data请求import { useState } from react; import { api } from wasp/client/api; export const MainPage () { const [name, setName] useState(); const [file, setFile] useStateFile(); const handleSubmit async (e: React.SubmitEvent) { e.preventDefault(); if (!file) return; const formData new FormData(); formData.append(name, name); formData.append(file, file); const data await api .post(/api/upload, { body: formData }) .json{ fileExists: boolean }(); alert(JSON.stringify(data, null, 2)); }; return ( form onSubmit{handleSubmit} input typetext placeholderName value{name} onChange{(e) setName(e.target.value)} / input typefile onChange{(e) setFile(e.target.files?.[0])} / button typesubmitUpload/button /form ); };要点FormData.append(file, file)的字段名必须与upload.single(file)中的参数一致否则 Multer 收不到文件文本字段如name会出现在req.body中文件信息出现在req.file中不要手动设置Content-Type: multipart/form-data浏览器与 fetch 会自动生成带 boundary 的正确请求头记得在main.wasp.ts中把MainPage通过route挂到对应路径示例中是/。4. 自定义上传设置4.1 更改存储位置修改 Multer 实例的dest即可const upload multer({ dest: my-custom-uploads/ });dest决定文件落盘目录。若需要更精细的控制如按文件类型/时间分目录、自定义文件名、控制是否落盘可使用 Multer 的storage选项——diskStorage支持通过destination与filename回调定制存储策略memoryStorage则把文件保存在内存中适合后续转存云存储/对象存储但受服务端内存上限约束import multer from multer; import path from path; const storage multer.diskStorage({ destination: (req, file, cb) cb(null, uploads/), filename: (req, file, cb) cb(null, ${Date.now()}-${file.originalname}), }); const upload multer({ storage });4.2 限制文件大小通过limits.fileSize设置上限单位字节const upload multer({ dest: uploads/, limits: { fileSize: 5 * 1024 * 1024, // 5MB limit }, });超出限制时 Multer 会触发LIMIT_FILE_SIZE错误服务端需配合错误处理中间件返回友好的响应例如413 Payload Too Large。此外还可利用limits.files控制单次请求文件总数。4.3 过滤文件类型通过fileFilter回调按 MIME 类型白名单校验cb(null, true)放行、cb(new Error(...))拒绝const upload multer({ dest: uploads/, fileFilter: (req, file, cb) { if (file.mimetype.startsWith(image/)) { cb(null, true); } else { cb(new Error(Only images are allowed)); } }, });需要提示file.mimetype来自客户端声明只应作为第一道防线安全敏感场景建议在服务端读取文件头做二次校验可借助file-type等库。4.4 处理多个文件将中间件从upload.single(file)换成upload.array(files, 10)即可接收同一字段名下的最多 10 个文件export const configureFileUploadMiddleware: MiddlewareConfigFn (config) { config.set(multer, upload.array(files, 10)); // Max 10 files return config; };此时文件数组位于req.files前端需用input typefile multiple /并遍历files依次append到FormData。若需要多个字段各带文件如头像 附件可使用upload.fields([{ name: avatar, maxCount: 1 }, { name: attachments, maxCount: 5 }])。更多选项可查阅 Multer 官方文档原指南末尾亦给出该指引。5. 原理纵深上传接口在 Wasp 中是如何被生成的为确认上述配置的真实行为可以沿着本仓库的生成链路逐层验证规范解析main.wasp.ts中的apiNamespace/api声明经 waspc/src/Wasp/AppSpec 解析为AppSpec内部数据ApiNamespace、Api均实现Inspectable可用wasp inspect查看。代码生成ApiRoutesG.hs 将每条声明映射为生成数据命名空间生成namespacePath与导入语句路由生成routeMethod、routePath、entities、usesAuth、middlewareConfigFn等字段。模板渲染apis/index.ts 模板 最终产出 Express 代码命名空间中间件通过router.use(/api/upload, globalMiddlewareConfigForExpress(fn))批量挂载每条路由通过defineHandler包装并把entitiesprisma.model与可选user注入 handler 的context鉴权开启时auth中间件位于自定义中间件之前。中间件合并globalMiddlewareConfigForExpress 先克隆全局默认中间件 Map避免污染其他路由再调用用户提供的MiddlewareConfigFn得到最终数组。因此每个路由/命名空间拿到的是全局默认中间件 你的定制的副本config.set(multer, ...)本质是在默认列表helmet/cors/logger/json/urlencoded/cookieParser基础上追加 Multer。这一链路也解释了官方指南 middleware-config.md 中的警告全局中间件修改会影响所有 Operations 与 API而apiNamespace/api级定制只作用于指定路径是文件上传这类局部需求的首选方案。6. 常见问题与最佳实践req.file始终为undefined优先检查前端FormData.append的字段名与upload.single(file)的参数是否完全一致其次确认请求确实以multipart/form-data发送不要手动覆盖Content-Type。上传接口报 413 / 请求体过大检查limits.fileSize是否过小同时留意反向代理如 Nginx的client_max_body_size限制。文件权限与目录存在性dest目录需保证服务端进程有写入权限生产部署如 Fly.io建议使用持久化卷或对象存储避免容器重启导致文件丢失。鉴权与安全若上传接口需要登录api声明默认继承全局鉴权设置可用auth: false显式关闭始终限制文件大小与类型对文件名做净化防路径穿越并对文件内容做病毒/恶意内容扫描。持久化元数据把req.file返回的路径、originalname、size等信息写入数据库实体便于后续查询与下载路由使用。至此你已掌握在 Wasp 中集成 Multer 完成文件上传的完整方案从main.wasp.ts声明式路由到服务端中间件钩子与处理器再到前端FormData上传并可基于limits、fileFilter、storage等选项自由定制上传行为。【免费下载链接】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),仅供参考