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

GraphQL文件上传报错:POST body缺失/Content-Type无效问题求助

GraphQL文件上传错误排查与解决方案

问题根源

你的报错"POST body missing, invalid Content-Type, or JSON object has no keys."核心原因是:Apollo Server v3+ 已移除内置文件上传支持,默认无法解析multipart/form-data格式的请求,导致请求体解析失败。同时你的代码未配置文件上传所需的中间件,也未正确处理Upload类型的解析逻辑。

解决方案步骤

1. 安装必要依赖

安装处理文件上传的核心包及TypeScript类型定义(TS项目):

npm install graphql-upload
npm install -D @types/graphql-upload

2. 修改服务器配置(改用Apollo Server Express)

apollo-server通用版配置灵活性有限,建议切换到apollo-server-express并添加文件上传中间件:

require("dotenv").config();
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { typeDefs, resolvers } from "./schema";
import { getUser } from "./schema/User/User.Utils";
import { graphqlUploadExpress } from "graphql-upload";

async function startApolloServer() {
  const app = express();
  // 添加文件上传中间件,可设置文件大小/数量限制
  app.use(graphqlUploadExpress({ maxFileSize: 10 * 1024 * 1024, maxFiles: 1 }));

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: async ({ req }) => {
      return {
        loggedInUser: await getUser(req.headers.token as string),
      };
    },
    // 禁用Apollo默认上传处理,改用graphql-upload中间件
    uploads: false,
  });

  await server.start();
  server.applyMiddleware({ app });

  const PORT = process.env.PORT || 4000;
  app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}${server.graphqlPath}`);
  });
}

startApolloServer();

3. 正确处理Resolver中的Upload字段

Upload类型是一个包含文件流、文件名等属性的Promise,需解析后处理文件:

import client from "../../../client";
import bcrypt from "bcrypt";
import { IAccount, IContext } from "../User.interface";
import { protectResolver } from "../User.Utils";
import { createWriteStream } from "fs";
import path from "path";
import { mkdirSync } from "fs";

// 确保uploads目录存在
const uploadDir = path.join(__dirname, "../../../uploads");
mkdirSync(uploadDir, { recursive: true });

export default {
  Mutation: {
    editProfile: protectResolver(
      async (
        _: unknown,
        {
          firstName,
          lastName,
          userName,
          email,
          bio,
          avatar,
          password: newPassword,
        }: IAccount,
        { loggedInUser }: IContext
      ) => {
        let avatarUrl = null;
        // 解析并保存上传的文件
        if (avatar) {
          const { createReadStream, filename } = await avatar;
          const stream = createReadStream();
          const filePath = path.join(uploadDir, filename);
          
          // 写入文件到服务器(生产环境建议用云存储如S3)
          await new Promise((resolve, reject) => {
            stream.pipe(createWriteStream(filePath))
              .on("finish", resolve)
              .on("error", reject);
          });
          avatarUrl = `/uploads/${filename}`;
        }

        let uglyPassword = null;
        if (newPassword) {
          uglyPassword = await bcrypt.hash(newPassword, 10);
        }

        const updateUser = await client.user.update({
          where: { id: loggedInUser?.id },
          data: {
            firstName,
            lastName,
            userName,
            email,
            bio,
            ...(uglyPassword && { password: uglyPassword }),
            ...(avatarUrl && { avatar: avatarUrl }), // 假设User模型有avatar字段存储路径
          },
        });

        return updateUser.id 
          ? { ok: true } 
          : { ok: false, error: "업데이트가 불가능합니다" };
      }
    ),
  },
};

4. 前端请求配置验证

如果使用Apollo Client,需用createUploadLink替代默认的createHttpLink:

import { ApolloClient, InMemoryCache } from "@apollo/client";
import { createUploadLink } from "apollo-upload-client";

const client = new ApolloClient({
  link: createUploadLink({ uri: "http://localhost:4000/graphql" }),
  cache: new InMemoryCache(),
});

额外注意事项

  • 确保服务器uploads目录存在且有读写权限
  • 生产环境优先使用云存储服务(如AWS S3)替代本地文件存储
  • 不要手动设置multipart/form-data的边界,浏览器会自动处理

内容的提问来源于stack exchange,提问作者user22127283

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:43:18