Skip to content

Latest commit

 

History

History

fileUploads

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

File upload with GraphQL

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

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

Поле с Base64

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

  • как минимум +30% в размере передаваемых данных по сети
  • кушаем процессор как на клиенте так и на сервере
  • тратим память

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

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

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

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

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

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

  • на вашем сервере генерируете ссылку (вот есть статья с подробностями) для аплоада картинки signedUrl:
    • чтоб кто попало не мог вам грузить что попало
    • чтоб задать правила валидации размера файла и content-type
    • чтоб передать дополнительные метаданные (id пользователя)
    • чтоб указать место куда и с каким именем положить картинку в bucket'е на S3
  • в Amazon S3 настраиваете hook на появление нового файла, который вызовет lambda-функцию с нарезкой картинки в нужные размеры. Как это делается можно почитать тут и тут
  • для вашего клиента пишем скрипт помогайку, который на входе получает объект файла, лезет на сервак за подписанной ссылкой для загрузки, загружает картинку, отслеживает прогресс и сообщает о завершении. Пример смотрите в GIST'е.
  • если используете React, то можно обернуть апи загрузки файла в компоненту. Опять пример есть в GIST'е
  • и вот уже после успешной загрузки можем отправить в наш GraphQL ссылки на загруженные и нарезанные картинки.

graphql-multipart-request-spec

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

  • вам нужно чутка докрутить ваш GraphQL-сервер
  • а также научить ваш клиент правильно паковать GraphQL-запрос и файлы в multipart/form-data.

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

  • вложенность файла на любую глубину (правда только через variables)
  • отмену загрузки файлов
  • получение файла в виде Stream в вашем resolve-методе

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

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

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

  • настраиваю express коммит 1
  • добавляю Upload тип к GraphQL-схеме коммит 2
  • для примера добавляю мутацию в схему коммит 3
  • и добавляю два примера с запроса от клиента через CURL и Postman коммит 4

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

Если для клиента вы используете 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!