Как загружать изображения через GraphQL в React и TypeScript

    При разработке на React + TypeScript часто возникает задача передачи изображений и файлов на сервер. Многие разработчики по умолчанию используют кодировку base64, передавая строку через GraphQL-мутацию. Однако этот подход неэффективен: размер данных увеличивается на 33%, а большие файлы замедляют работу приложения. Правильное решение - передача файлов в бинарном формате (Blob, File) через GraphQL, но как это сделать без REST API и с поддержкой TypeScript?

    Почему base64 - не лучший выбор для загрузки файлов

    Передача изображений строкой base64 через GraphQL - простой, но ресурсоёмкий метод. Во-первых, увеличивается объём передаваемых данных. Во-вторых, парсинг и декодирование на сервере требуют дополнительной памяти. Для маленьких аватарок или иконок это допустимо, но для фотографий высокого разрешения или документов лучше использовать бинарную передачу.

    Apollo Upload Client и TypeScript: как решить проблему

    Библиотека apollo-upload-client предоставляет функцию createUploadLink, которая заменяет стандартный HTTP-линк Apollo и позволяет отправлять файлы через FormData. Однако её типовая поддержка в официальной версии оставляет желать лучшего - типы не экспортируются корректно для TypeScript. Решение есть: установите пакет @types/apollo-upload-client или используйте apollo-upload-client версии 17+, где типы уже встроены.

    Пошаговая инструкция: загрузка файла через GraphQL в React + TS

    1. Установка зависимостей

    Выполните команду:

    npm install apollo-upload-client @types/apollo-upload-client

    2. Настройка Apollo Client

    Замените стандартный createHttpLink на createUploadLink:

    import { ApolloClient, InMemoryCache } from '@apollo/client';
    import { createUploadLink } from 'apollo-upload-client';
    
    const client = new ApolloClient({
      link: createUploadLink({ uri: 'http://localhost:4000/graphql' }),
      cache: new InMemoryCache()
    });

    3. Создание мутации на сервере

    На серверной части (например, Apollo Server) определите тип Upload и мутацию:

    import { GraphQLUpload } from 'graphql-upload';
    
    const typeDefs = gql`
      scalar Upload
    
      type Mutation {
        uploadFile(file: Upload!): String!
      }
    `;
    
    const resolvers = {
      Upload: GraphQLUpload,
      Mutation: {
        uploadFile: async (_, { file }) => {
          const { createReadStream, filename } = await file;
          // сохраните файл и верните URL
          return `https://example.com/uploads/${filename}`;
        }
      }
    };

    4. Отправка файла с клиента

    В React-компоненте используйте мутацию с типизацией:

    import { useMutation, gql } from '@apollo/client';
    
    const UPLOAD_FILE = gql`
      mutation UploadFile($file: Upload!) {
        uploadFile(file: $file)
      }
    `;
    
    function UploadComponent() {
      const [uploadFile] = useMutation(UPLOAD_FILE);
    
      const handleFileChange = (e: React.ChangeEvent) => {
        const file = e.target.files?.[0];
        if (file) {
          uploadFile({ variables: { file } });
        }
      };
    
      return ;
    }

    Что делать, если apollo-upload-client не подходит

    Если вы не хотите использовать стороннюю библиотеку или у вас возникают конфликты типов, есть альтернативы. Можно реализовать кастомный Apollo Link, который преобразует запрос в FormData. Или использовать GraphQL Multipart Request напрямую - спецификация поддерживается многими серверами. Ещё один вариант - передавать файл как Blob через переменную типа String, но это менее эффективно, чем нативный Upload.

    Распространённые ошибки и их решение

    • TypeScript ругается на типы: убедитесь, что установлен @types/apollo-upload-client или используйте версию 17+.
    • Файл не отправляется: проверьте, что сервер поддерживает скаляр Upload и настроен на приём multipart-запросов.
    • Размер файла слишком большой: настройте лимиты в Apollo Server через maxFileSize.

    Использование бинарной загрузки через GraphQL в React и TypeScript не только возможно, но и рекомендуется для производительности. Избегайте base64 для больших файлов - это упростит код и ускорит работу приложения.

    Часто задаваемые вопросы