Я всю жизнь писал только бэк и подкапотщину - будь это классический КРУД, хайлоад, CLI, [вставьте свое]... И для любых сетевых взаимодействий чаще всего люди думают именно прикладными вещами - GRPC, REST, Kafka, не задумываясь об этом глубже - супер удобные инструменты с защитами от дураков и прочими радостями

Но тут спохватился я писать фронт - подключать свое же к себе же. И в этот момент я понял, насколько же это сложно, муторно и, главное, НЕУДОБНО взаимодействовать REST'ом

ЗАЧЕМ ОН НУЖЕН?? - У нас нет удобного контракта общения (eg Proto, Avro) кроме Swagger, который нужно поддерживать с обеих сторон. Да и к тому-же, сложность взаимодействия с JSONом с ОБЕИХ СТОРОН - одна постоянно маршаллит, защищается, ищет поля, в то время другая боится резких обновлений, что строчка получения поля может превратиться в что-то вроде

resp?.body?[i]?.creds?.card?.number

В этот момент у меня возник вопрос: если gRPC решает эти проблемы, почему мы не используем его на фронте?

Попытки решения отсутствия контрактов

не буду вдаваться в подробности, большинство их +- трогало или знают, распишу плюсы и минусы подходов

Codegen/OpenApi/Swagger

Запихну это все в одно и напишу кратко - ЭТО НИКОГДА НИЧЕГО НЕ ГАРАНТИРУЕТ.

Фронт резко открыл код, сделал запрос, а там уже разраб, который ушел в отпуск, уже все поля красиво поменял, Сваггер не обновил и никому ничего не сказал.

Вы скажете - «Ну проблема в плохой организации работы» - как бы да, но у всех бывают дни, когда чо-то идет не так, мы все люди))

GraphQL

Известная голубая компания ЛицоКнига когда-то решила проблемы разработчиков интерфейсов - строгая схема, типизация, codegen, introspection

Фронтендеры были относительно счастливы (хотя и своих бед с GQL хватало), но кому явно досталось - бэк
Разработчики серверной части лишились КОНТРОЛЯ

  1. клиент сам выбирает, что ему нужно и в каком количестве

    сегодня это было

    { user { id name } }

    а завтра станет

    { user { posts { comments { author { posts { comments { … } } } } } } }

    по итогу ты опять созваниваешься с фронтом и говоришь «Это дергай, а это старайся не дергать»

  2. отсутствует прямое соответствие моделям данных

    резолверы, маппинг, аггрегации, не дай бог юнион тайпы - вокруг всего теперь танцую Я, а не ОН

  3. N+1

    Почти везде, почти всегда

Да, эти проблемы решаются - DataLoader закрывает N+1, persisted queries и depth limiting ограничивают клиента. Но каждое из этих решений - это дополнительный слой сложности на бэке, которого не было с REST
Про неудобство в некоторых языках я вообще молчу (гляньте пакет на го)

gRPC-Web

Так вот же оно! К чему статья??

Надеюсь, что я не многих удивлю, но это - ненастоящий gRPC

Это - лютейшего вида trade-off! Да, с виду у нас все есть: Protobuf, codegen, контракт. А что под капотом?

Отсутствие полноценного HTTP/2

Да, все верно. Хоть браузеры давно поддерживают даже 3ю версию протокола, но полного контроля нам никто не давал. А это значит, что мы теряем

  1. Опять Контроль - мы НЕ управляем соединением, НЕ управляем фреймами, НЕ управляем стримами. Клиент даже не знает, какую версию HTTP изволит использовать браузер, это выглядит как обычный fetch

    fetch(“/api”)

  2. Одни из самых главных фишек HTTP/2 - управление мультиплексированием и бинарными фреймы.

    Браузер → прокси работает по HTTP/1.1, зато прокси → бэк использует полноценный HTTP/2. Выигрыш есть, но только на конце - там, где нам он нужен меньше всего

  3. Просто с места ничего не заработает - нужна инфраструктура, которые это будет поддерживать (как минимум Proxy, который это будет правильно принимать и понимать)

Итог: gRPC перестаёт быть транспортом и превращается в формат данных. Мы приобретаем лишь гемор без плюсов. Как говорил один классик:

Можно. А зачем?

А почему так-то?

Тут и стало понятно - проблема не в REST, не в GraphQL и даже не в gRPC

Главная проблема - САМ БРАУЗЕР

Ответ почему так получилось крайне прост - БЕЗОПАСНОСТЬ, а возможно даже параноидальность

Каждый раз заходя на любой сайт мы запускаем код на своем устройстве: с любого источника, от любого автора, с доступом к сети

Если мы дадим ПОЛНЫЙ ДОСТУП к HTTP/2-3, мы соответственно даем также доступ к

  • Любым TCP соединениям

  • Любым долгоживущим соединениям

  • Управлению HTTP/2 фреймам с плохим умыслом

А с таким набором инструментов на любом ПК мы можем

  • сканировать внутреннюю сеть пользователя

  • ломиться в localhost

  • обходить firewall

  • устраивать DDoS чужими руками

А почему с HTTP/1.1 нет проблем?

Тут два главных столпа

  1. Sandbox

    JS работает в своей песочнице, в которой нет доступа ни к сокетам, ни к файловой системе (ну почти), к процессам и вообще ограничен контроль над сетью

  2. Абстракция

    Нам дают маленький набор инструментов (и тот ограничен, как к пр. WebSocket). Браузер все решает САМ

Тогда в принципе зачем браузеру нужны HTTP/2-3?

Примерно тут уже в голове небольшой диссонанс - если все фичи заблокированы, то тогда зачем они в принципе существуют?

А все крайне просто - HTTP/2-3, QUIC и прочие радости существуют не для кода, исполняемого в браузере, а для самого браузера. Мы не можем пользоваться этим самим, а он все сам решает

  • мультиплексирование

  • сжатие заголовков

  • бинарный формат

    и многое другое, но все ведет к одному - только для ускорения самому, и не более

Панацея (или что с этим делать)

Редко, но можно найти в использовании красивые лаконичные решения

  1. tRPC

    если у тебя монорепо TypeScript, это почти идеальный контракт без кодогенерации. Работает поверх обычного HTTP, типы шарятся напрямую через импорт

  2. Connect protocol (от Buf)

    надстройка над gRPC, которая умеет работать с обычным fetch. Официально поддерживается в браузере, имеет нормальный codegen

  3. WebTransport

    экспериментальный API, который даёт браузеру доступ к HTTP/3 потокам в sandboxed режиме. Пока сырой, но это именно то направление, куда движется индустрия

Что в итоге?

Альтернативы существуют - tRPC, Connect, WebTransport. Но REST никуда не уйдёт в ближайшее время, потому что универсальность важнее удобства. Браузерные ограничения - это симптом, а корень глубже: мы до сих пор не договорились, кто отвечает за контракт (да, мы тоже в этом виноваты)

Но все идет вперед. WebTransport потихоньку стабилизируется и даёт браузеру то, чего у него никогда не было - доступ к потокам поверх HTTP/3 в sandboxed режиме. Connect protocol от Buf набирает комьюнити, а TypeScript-монорепозитории с tRPC уже сейчас показывают, как выглядит мир без ручного поддержания контрактов (хоть и крайне редко)

Возможно, через пять лет мы будем смотреть на голый REST так же, как сейчас смотрим на XML-RPC. А возможно - нет, и curl останется главным инструментом отладки ещё очень долго

Также буду благодарен за подписку на мой тгк, если вам близки такие мини статьи и факты из моего личного опыта работы)

Комментарии (19)


  1. EnChikiben
    05.04.2026 13:26


  1. Tishka17
    05.04.2026 13:26

    если gRPC решает эти проблемы, почему мы не используем его на фронте?

    так он почти ничего не решает, а местами наоборот проблем приносит


    1. en1yee Автор
      05.04.2026 13:26

      проблемы с gRPC естественно есть. Я больше рассмотрел это через призму backend разработки, где он ну супер хорош

      буквально хотелось взять этот прото-файлик общий и просто везде использовать, в концепции будто бы кайф, не?

      ну и далее я в статье в принципе описал, какие трбаблы и альтернативы


      1. nikulin_krd
        05.04.2026 13:26

        Если же у вас монорепа, то что вам мешает использовать http, но с типизацией общими DTO?


      1. Tishka17
        05.04.2026 13:26

        берете openapi спеку и везде используете. в чем разница?


    1. Heggi
      05.04.2026 13:26

      А какие проблемы он вам приносит?


  1. savostin
    05.04.2026 13:26

    resp?.body?[i]?.creds?.card?.number

    Так в protobuf тоже все поля опциональные by design.


    1. en1yee Автор
      05.04.2026 13:26

      не совсем
      в proto3 точно (за остальные не скажу) используются zero-value
      ист: https://protobuf.dev/programming-guides/proto3/#default

      if the encoded message bytes do not contain a particular field, accessing that field in the parsed object returns the default value for that field

      сами байты данных опциональны, но они от этого не становятся null/undefined

      как раз для такого есть optional и тогда да


      1. Heggi
        05.04.2026 13:26

        Это для простых типов. Вложенные message все nullable


        1. en1yee Автор
          05.04.2026 13:26

          да, бесспорно, тут криво сказал, больше зацепился за «by design». но тем не менее, вложенные в конечном счете тоже состоят из простых типов

          по итогу, мы 100% знаем, где null может быть, а где - нет


    1. nikulin_krd
      05.04.2026 13:26

      Да и для такого в JS есть class-transformer и class-validator, чтобы не выстрелить себе в ногу


  1. artptr86
    05.04.2026 13:26

    в то время другая боится резких обновлений, что строчка получения поля может превратиться в что-то в роде

    resp?.body?[i]?.creds?.card?.number

    Так у protobuf все поля тоже концептуально Nullable — получится такая же толпа элвисов


  1. Tetsuzin72
    05.04.2026 13:26

    Очень интересно. Вероятно, мне предстоит задача отрисовки большого количесвта данных (~100 MB за раз) на канвасе. Данные, естественно, хочется в бинарном виде передавать. Думал про gRPC, но конкретно к задаче пока не приступал. Ваша статья заставляет задуматься....


    1. en1yee Автор
      05.04.2026 13:26

      спасибо за ос! крайне приятно знать, что кому-то мог помочь или натолкнуть на мысли)


  1. BenGunn
    05.04.2026 13:26

    Пробовали в проекте этот ваш gRPC. Куча проблем с невероятно жирными зависимостями. По итогу заменили на HTTP/3 + flatbuffers(они же с gRPC юзались. В целом ничего не потеряли, а зависимостей в проекте стало сильно меньше.


    1. en1yee Автор
      05.04.2026 13:26

      использовали для сервис-сервис? или браузер тут как-то участвует? если бек-бек, то тогда немного другая история и хороший повод для статьи)

      к сожалению, с браузером flatbuffers несильно бы помог, ограничения браузера те же


      1. BenGunn
        05.04.2026 13:26

        У нас просто игра которая шлёт всякую статистику на бэк. Надо было просто кинуть в бэк flatbuffer и даже какой то ответ не нужен был. По итогу отказались от решения так как оно начало доставлять проблемы в очень неожиданном месте. Потребовалось обновить библиотеку которая была в зависимостях у gRPC и после обновления понеслись крэши игры. Мы конечно потратили пару недель на поиск проблемы но по итогу решили что HTTP/3 клиент реализованный поверх libcurl будет надежней ибо у нас уже была готова реализация и она оказалась проще, понятней и тоньше. Самое главное она не тащила за собой absel который в нашем случае доставлял проблемы.


        1. Heggi
          05.04.2026 13:26

          А что у вас за стек, что grpc тащит жирные зависимости, да еще и крешит игру?


  1. Heggi
    05.04.2026 13:26

    Используем grpc-web и никаких проблем. Чтобы обмениваться json данными с беком, его за глаза. Даже серверные стримы работают нормально (минус websocket). То что там под капотом http1.1 (на самом деле нет, там тот же http2.0, только к специфичным штучкам доступа нет) вообще пофигу.

    Да, надо ставить прокси (envoy) сайдкаром к сервису бекэнда или включать поддержку (dotnet и rust) в самом проекте. Но это мелочи на фоне секаса с поддержанием контрактов.