🆙

GraphQLにおけるファイルアップロードの仕様および実装

2022年1月現在、GraphQLでファイルをアップロードするための仕様は公式には定義されていません。
なのでクライアント側とサーバー側でファイルをどのように扱うかを自分で決めてそれに沿った実装を行う必要があります。
非公式ではありますがGraphQLでのファイルアップロードのよく参照される仕様があるのでこちらに沿って実装を行うとライブラリの恩恵等が得られるのでおすすめです。
 
この仕様に沿って実装されたライブラリ郡もまとめられています。

JavaScriptクライアント側の実装について

GraphQLのjsクライアントライブラリとしてよく使われるのはApolloとRelayです。
Apolloはgraphql-multipart-request-specに則ってファイルアップロードを行うライブラリが提供されているのでこちらを利用するとスムーズに実装ができます。
 
対してRelayにはライブラリは用意されていない(僕が探した限り)ので自前で実装する必要があります。
ですが仕様を理解すれば実装はそれほど難しくありません。
↓のページではRelayクライアントを使いgraphql-multipart-request-specに則って実装する方法を紹介しています。
RelayでGraphQL multipart request specificationに沿ったファイルアップロードを行う - 私が歌川です
2021/7/17現在、GraphQL APIを通じてファイルをアップロードする方法は規定されていない。そのため、GraphQL APIでファイルアップロードを行いたい場合は何らかの工夫を行う必要がある。 GraphQL APIリクエストの変数中にファイルをエンコードして送信することで、ファイルをアップロードするという目的は達成できそうに見える。が、この方法では巨大なJSONをリクエストボディとして送信する必要があり、効率がよくない。また、各種APIサーバーがファイルアップロードのためにメモリを効率的に利用する実装*1 を行っていても、その恩恵を受けづらい。 GraphQL multipart request specification という仕様に則ると、リクエストの変数中にファイルをエンコードして送信する方法と比べて、効率的にファイルをアップロードすることができる。仕様の解説やAPIサーバー側の実装方法については以下の記事が詳しいので、この記事では割愛する。 この記事では、 Relay でGraphQL multipart request specificationに沿ってファイルをアップロードする方法を解説する。クライアントの実装言語としてTypeScriptを用いる。 この記事中でのスキーマは以下。 Upload scalarがアップロードするファイルを表している。 以下は2つのファイルをアップロードするmutationのコード例である。通常のmutationと同様に commitMutation 関数を使ってファイルをアップロードできるが、気をつけるべきポイントがいくつかある。 Upload scalarには null を渡す mutationの入力のうち Upload 型のフィールドには、アップロードするファイルを表すオブジェクト ( File や Blob など) ではなく null を渡す必要がある。ではどうやってファイルをアップロードするのかというと、後述する uploadables を使って渡す。 relay-compilerでファイルを生成する際には --customScalars.Upload null オプションを指定しておくと間違えなくて済む。 relay-compiler --schema ./schema.graphql --src src --language

サーバー側の実装について

僕はサーバー側をRuby on Railsとgraphql-ruby gemを使い実装しました。
graphql-ruby gemでgraphql-multipart-request-specに則ってファイルアップロードを実装するためのライブラリとしてapollo_upload_server-ruby gemがあります。\
 
これは名前にapolloとついていますがApollo専用というわけではなく、graphql-multipart-request-specに準拠したリクエストを処理するためのライブラリなので、クライアントがRelayでもspecに準拠したリクエストであれば問題なく使えます。
このgemはRailsのMiddlewareとして動作し、Gemfileに記載すれば自動的に使用されます。
あとはgraphql-ruby gemのscaler typeとして以下のようなモジュールを用意し、これをファイルアップロードのargumentの型に指定すれば動きます。

その他に参考にしたページ

GraphQL でファイルをアップロードしたい - esm アジャイル事業部 開発者ブログ
こんにちは、hibariya です。さいきん GraphQL でのファイルアップロード方法について知りたいなと思いちょっと検索してみたところ、すぐにはこれといった方法に辿りつけなかったので気になって調べました。手元で試した感じだと GraphQL multipart request specification という仕様が良さそうでしたので、今日はそのことについて紹介したいと思います。 現在のところ、GraphQL の仕様ではファイルアップロード方法については特に規定されていないため、自分たちで方法を決めて実装する必要があります。が、これは少し面倒です。HTTP で GraphQL を使う場合、サーバに渡す値はたいてい JSON のようなフォーマットで シリアライズしますが、Fileオブジェクトはそのまま JSONには エンコード できません。 ですから、GraphQL でファイルをアップロードしようというときには少し工夫が必要になります。方法としてよく挙げられているのは以下の3つでしょうか。 最初の方法1は、そのままでは シリアライズできないファイルを Base64 エンコードすることで JSON として扱えるようにする方法です。クライアントとサーバ間の通信方法を大きく変える必要がなさそうで、シンプルそうに見えます。ただ、巨大なファイルを扱う場合には、あまり考えずにサーバ側のアプリケーションを実装すると巨大な JSON をオンメモリで扱うことになって面倒そうな気がします *1 。あとはファイルサイズが若干増えますし、ファイル名のようなメタ情報を自分で渡さないといけないのも少し面倒ですね。 次の2はファイルアップロードを GraphQL API では扱わず、別途用意した REST API などを併用するという方法です。確かに方法のひとつとしては考えられますが、使い方によっては、元々ひとつだった トランザクション を分ける必要が出てくるなど用途を選びそうです。 最後の3は GraphQL のリク エス トを multipart/form-data として送る方法です。ここで紹介する GraphQL multipart request specification は、この方法を採用しています。 リク エストに multipart/form-data を使う方法であれば、巨大なファイルは (例えば Ruby なら) Rack のレイヤで少しずつファイルに書き出される *2 ため、サーバサイドのメモリ使用量についての心配はひとまず置いておけます。また、この方法にはいくらか認知されている仕様として、 GraphQL multipart request specification というものがあります。 仕様が定めているのは、multipart でどうやって GraphQL リク エストを送るのか、特に GraphQL の変数の値としてとしてどうやってファイルを マッピング するのかというところです。仕様そのものは単純なので、例をまじえて説明します。 README とほぼ同じですが、冒頭の例で説明するとクライアントは以下のような感じの multipart message をサーバに送ることになります。 --------------------------cec8e8123c05ba25 Content-Disposition: form-data; name="operations" { "query": "mutation CreateMessage($input: CreateMessageInput!)