GraphQL使用ApolloServer上传文件时$file变量无效报错如何解决
问题成因
该报错的本质是传递给$file变量的是未被正确解析的上传Promise实例,没有被GraphQLUpload标量识别为合法的上传值,具体触发原因通常为以下几种:
- 中间件加载顺序错误:
graphqlUploadExpress中间件需要在Apollo Server挂载到Express实例之前完成注册,顺序错误会导致文件上传请求没有被预先解析,直接传到Apollo的参数校验逻辑时就会被识别为无效值。 - 类型实例不匹配:如果你使用了TypeGraphQL、NestJS等框架,框架内置导出的
GraphQLUpload类型和你从graphql-upload包导入的类型不是同一个实例,参数校验时会因为类型不匹配抛出错误。 - 版本兼容问题:
apollo-server3.x及以上版本对上传的处理逻辑有较大改动,和旧版graphql-upload存在兼容问题;同时部分版本的TypeGraphQL对嵌套在自定义InputType内的GraphQLUpload类型解析存在缺陷。 - 请求格式错误:客户端上传请求没有使用
multipart/form-data格式,或者请求参数的结构不符合GraphQL multipart请求规范。
解决方案
- 调整中间件加载顺序
确保中间件注册顺序如下,先注册graphqlUploadExpress,再挂载Apollo Server的中间件:
// 先注册上传解析中间件 app.use(graphqlUploadExpress({ maxFileSize: 10000, maxFiles: 10 })) // 再挂载Apollo Server const server = new ApolloServer({ schema, context, uploads: false }) await server.start() server.applyMiddleware({ app })
- 统一Upload类型引用
直接在全局替换框架内置的Upload类型,确保所有地方用的都是graphql-upload导出的实例:
// 在InputType定义文件中,确认没有从其他包导入GraphQLUpload,仅保留graphql-upload的导入 import { FileUpload, GraphQLUpload } from 'graphql-upload' // 如果是NestJS项目,还需要在模块定义中覆盖默认的Upload标量 @Module({ providers: [ { provide: 'GraphQLUpload', useValue: GraphQLUpload, }, ], })
- 调整Mutation参数结构,规避嵌套Input解析bug
暂时把Upload字段从自定义InputType中提出来,直接作为Mutation的参数:
@Mutation(() => ReturnType) uploadFileAndData( @Ctx() ctx: Context, @Arg('file', () => GraphQLUpload) file: FileUpload, @Arg('data') data: Omit<MyInputType, 'file'>, // 调整InputType移除内部的file字段 ): Promise<ReturnType> { return functionWithMagic({ ctx, data: { ...data, file } }) }
- 校验版本兼容性
确保依赖版本匹配:
graphql-upload版本不低于15.0.0apollo-server-express版本不低于3.6.0type-graphql版本不低于1.1.1
- 校验客户端请求格式
确认客户端请求的Content-Type为multipart/form-data,且参数结构符合GraphQL上传规范,以Axios为例:
const formData = new FormData() formData.append('operations', JSON.stringify({ query: `mutation uploadFileAndData($file: Upload!, $data: MyInputType!) { uploadFileAndData(file: $file, data: $data) { // 填写需要返回的字段 } }`, variables: { file: null, data: { /* 填写其他非文件参数 */ } } })) formData.append('map', JSON.stringify({ '0': ['variables.file'] })) formData.append('0', file) axios.post('/graphql', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
内容的提问来源于stack exchange,提问作者Antoni
相关产品推荐
相关产品推荐

