conf-talks

File upload with GraphQL

Частенько в группах по GraphQL возникает вопрос, - “А как загружать файлы?”. Если залезть в спецификацию GraphQL, то про загрузку файлов вы там ничего не найдете. И на это есть причина, GraphQL сильно точился на передачу типизированных и связанных между собой данных. А также старался никак не ограничивать пользователей в способе загрузки файлов. Ведь спецификация реализовывается на куче языков, и что для одного языка может быть хорошо, то для другого - беда.

Но это не значит, что с GraphQL нельзя использовать передачу файлов. Можно, но это уже другой уровень абстракции. Разработчик сам волен выбирать для своего приложения способ загрузки файлов. Ведь загрузка файлов зависит не только от того как ее может принять сервер, но и от того как клиент может передать файл.

Поле с Base64

Самый спорный способ передачи файлов. На клиенте вы берете файл, энкодите его в base64 и передаете как обычную строку на сервер. На сервере проделываете похожие операции, только в обратном порядке. Минусов у этого подхода валом:

При этом можем столкнуться с тем, что сервер принимает только GET-запросы. Если используете graphql-express, то попадете на ограничение в 100kb на тело запроса. Если происходит логирование запросов, то файлы пишутся в лог.

Грузить через REST, а в GraphQL передавать только ссылки на загруженные файлы

Это рекомендованный способ передачи файлов. Вы грузите свой файл в специальное хранилище через старый добрый REST, получаете ссылку на загруженный файл. И уже эту ссылку загруженного файла передаете в вашем GraphQL-запросе.

Есть уже куча инструментов и библиотек по загрузке файлов, которые поддерживают догрузку файла, отображение процесса загрузки. Тем более вы разделяете ваши серверные ендпоинты по задачам, где админы сервера с файловой загрузкой будут оптимизировать одним способом, а GraphQL ендпоинт совершенно другим.

Загрузка картинки и нарезка тумбнеилов

Давайте разберем довольно частую задачу по загрузке файлов - это загрузка картинки и нарезка тумбнеилов. Для себя самым быстрым, дешевым и готовым к любым нагрузкам способом обработки картинок я считаю загрузку картинок в Amazon S3 c использованием lambda-функций. Стоит копейки, масштабируется автоматом под любые нагрузки, когда не используется денег особо не просит, бэкапировать толком не нужно. Самое главное поднять проксю под раздачу картинок, чтоб не попасть на платный амазоновский траффик. От слов к делу, реализацию для Node.js можно посмотреть в GIST’e где реализована эта логика:

graphql-multipart-request-spec

Но иногда, прям очень сильно надо передавать файлы вместе с GraphQL-запросом. Т.е. вы готовы грузить свои API серваки на прием файлов и передачу их в свое файловое хранилище. Для этого вам необходимо будет немножко почитать, установить и настроить пару либ т.к.:

Jayden Seric написал хорошую спецификацию по загрузке файлов. В ней он использует multipart/form-data. Также для Node.js сервера он подогнал её реализацию с кучей няшек:

На стороне сервера

Самый простой способ - использовать apollo-server. Он под капотом добавляет в вашу схему GraphQLUpload тип и на стороне сервера засовывает переданные файлы в Stream перед тем как вызвать выполнение запроса через graphql() функцию пакета graphql.

А вот если вы не используете apollo-server, и хотите все сделать сами вручную, то вы можете посмотреть graphql-compose-boilerplate-upload по коммитам. Там я прикручиваю загрузку файлов к express-graphql по спеке выше в 10 строчек кода:

На стороне клиента

Если для клиента вы используете Apollo, то вам нужно добавить apollo-upload-client при инициализации ApolloClient (см. документацию).

Если Relay, то прикрутить как нибудь самостоятельно по спецификации. У вас есть реализация под Relay и вы готовы поделиться решением? То откройте Pull Request к этой статье или добавьте мидлевару к react-relay-network-modern. Спасибо.

Если межсерверное-взаимодействие, то посмотрите спецификацию. По ней написана следующая простенькая CURL-команда (пару банок пива и у вас все получится):

curl localhost:3000/graphql \
  -F operations='{ "query": "mutation ($poster: Upload) { createPost(id: 5, poster: $poster) { id } }", "variables": { "poster": null } }' \
  -F map='{ "0": ["variables.poster"] }' \
  -F 0=@poster.jpg

Освежаем в памяти

GraphQL изначально точился под передачу типизированных и связанных между собой данных в формате JSON. Для загрузки и передачи файлов лучше использовать старый добрый REST. Но если сильно нужно прикрутить загрузку файлов к GraphQL, то берите спецификацию по загрузке файлов от Jayden Seric.

Да кстати, а получать содержимое файлов вы тоже хотите через GraphQL в JSON’е? Или вам достаточно загружать их по ссылке, которые получите в GraphQL-ответе? Подумайте еще раз, может ну его нафиг, грузить ваши GraphQL-сервера передачей файлов?! Юзайте для файлов старый добрый REST!