Fastify提交multipart/form-data报body must be object错误
问题现象
使用fastify-multer配合JSON Schema处理可能携带文件的multipart/form-data请求时,无论如何调整配置,Fastify始终返回400 Bad Request错误,错误响应如下:
{ "statusCode": 400, "error": "Bad Request", "message": "body must be object" }
涉及的服务配置、路由定义、请求示例如下:
服务入口文件(index.ts)
const server = fastify(); server.register(require("@fastify/cors")); server.register(multer.contentParser).after(() => { if (!isProdEnv) { server.register(require("@fastify/swagger"), { /* ... */ }); } server.register(require("@fastify/auth")).after(() => { server.decorate("authenticateRequest", authenticateRequest); server.decorate("requireAuthentication", requireAuthentication); server.addHook("preHandler", server.auth([server.authenticateRequest])); server.register(indexRouter); server.register(authRouter, { prefix: "/auth" }); server.register(usersRouter, { prefix: "/users" }); server.register(listsRouter, { prefix: "/lists" }); server.register(postsRouter, { prefix: "/posts" }); server.register(searchRouter, { prefix: "/search" }); server.register(settingsRouter, { prefix: "/settings" }); }); }); server.setErrorHandler((err, req, res) => { req.log.error(err.toString()); res.status(500).send(err); });
/posts/create接口路由与Schema配置
const postsRouter = (server: FastifyInstance, options: FastifyPluginOptions, next: HookHandlerDoneFunction) => { server.post( "/create", { schema: { consumes: ["multipart/form-data"], body: { content: { type: "string" }, media: { type: "string", format: "binary" }, "media-description": { type: "string" } } }, preHandler: [server.auth([server.requireAuthentication]), uploadMediaFileToCloud] }, postsController.createPost ); next(); }; export default postsRouter;
请求示例(CURL)
curl -X 'POST' \ 'http://localhost:3072/posts/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoYW5kbGUiOiJ1bGtrYSIsInVzZXJJZCI6IjYyNGQ5NmY4NzFhOTI2OGY2YzNjZWExZCIsImlhdCI6MTY1NzEwNTg5NCwiZXhwIjoxNjU3NDA1ODk0fQ.A5WO3M-NhDYGWkILQLVCPfv-Ve-e_Dlm1UYD2vj5UrQ' \ -H 'Content-Type: multipart/form-data' \ -F 'content=Test.' \ -F 'media=@flame-wolf.png;type=image/png' \ -F 'media-description=' \
问题原因
报错和multer配置、请求格式本身无关,核心原因是路由处定义的请求体Schema不符合JSON Schema标准规范:
- Fastify内置使用Ajv做请求参数校验,按照JSON Schema规则,描述一个对象类型的参数时,必须显式声明
type: "object",且对象的所有属性需要挂载在properties字段下 - 当前配置直接把业务字段平铺在
body节点下,Ajv会判定body的Schema定义本身不是合法的对象类型描述,因此直接抛出body must be object的400校验错误
修复方案
- 修正路由处的body Schema结构,严格遵循JSON Schema格式要求,调整后的Schema配置如下:
schema: { consumes: ["multipart/form-data"], body: { type: "object", // 显式声明请求体为对象类型 required: ["content"], // 按需配置必填字段 properties: { // 所有请求字段统一放在properties节点下 content: { type: "string" }, media: { type: "string", format: "binary" }, "media-description": { type: "string" } } }, preHandler: [server.auth([server.requireAuthentication]), uploadMediaFileToCloud] }
- 调整Schema后如果仍存在异常,检查插件注册顺序:确保
multer.contentParser的注册逻辑在所有业务路由注册之前执行,避免插件作用域导致multipart解析器未生效。 - 检查上传中间件
uploadMediaFileToCloud的逻辑,确认没有在中间件执行过程中修改req.body的结构,导致校验阶段拿到的请求体不是合法对象。
调整完成后重启服务,使用原有CURL命令即可正常发起请求,不会再出现400报错。
内容的提问来源于stack exchange,提问作者Mr. X
相关产品推荐
相关产品推荐

