You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 16:57:19