前言

背景

在 Web 应用中,上传文件是非常常见的需求,而在使用 GraphQL 以后,应该如何实现上传文件的功能呢。

环境

  • apollo-server:2.x;
  • Altair:2.4.6;

后端

Apollo 直接提供了上传文件的功能,具体用法参见官方文档

后端在处理时需要注意两个地方:

  • 根据文档获取上传文件的文件名和 readable stream;
  • 为了避免内存溢出,不用全部读取到内存,然后再保存文件,而是将 readable stream 通过管道直接连接到 writable stream;
# schema.graphql
type mutation {
    uploadFile(file: Upload!): string!
}
// resolver.js
const mkdirp = require('mkdirp');
const localDir = './' // 当前文件夹

module.exports = {
  Mutation: {
    async uploadFile(_, { file }, ctx) {
        const { createReadStream, filename } = await file;
        const localFilename = uuidv1() + path.extname(filename);
        const filePath = localDir + localFilename;

        await mkdirp(localDir);
        const writable = fs.createWriteStream(filePath);
        await new Promise((resolve, reject) =>
          createReadStream()
            .pipe(writable)
            .on('error', err => reject(err))
            .on('close', resolve)
        );
        return filePath;
    },
};

前端

客户端

cURL

后端写好以后不想再构建一个前端项目来测试,想直接使用 Postman 或者 Insomnia 之类的调试工具做调试,但是因为涉及到文件,暂时都不支持原生 GraphQL 的写法,只有通过原生 form-data 的方式请求。

后来在资料中找到直接使用 curl 的请求方式。

$ curl localhost:7001/graphql \
  -F operations='{ "query": "mutation ($file: Upload!) { uploadFile(file: $file) }", "variables": { "file": null } }' \
  -F map='{ "0": ["variables.file"] }' \
  -F [email protected]

Altair

后来又在网上查找到了另外一款工具,叫做 Altair,算是为数不多的支持 Apollo 上传文件的客户端,语法也比较简单,直接在文件那里输入要替换的参数就可以了。由于我的把文件又封装了一层,试了一会才试出了封装一层的语法。Altair 提供了很多客户端,甚至包括浏览器的插件,为了方便,我就直接使用的 Web 端。

altair-upload

Vue

TODO

参考资料