REST или GraphQL: какой API выбрать

Чем REST и GraphQL отличаются: четырнадцать критериев, какой подход под какой проект, одна карточка товара через оба API, что настроить в каждом и частые ошибки.

Стек и технологии Обновлено

Коротко

REST и GraphQL — два способа отдавать данные сайту, приложению или другой системе. REST — набор адресов, по одному на ресурс: просто, кэшируется любым CDN, понятно каждому инструменту и стандарт для интеграций с CRM, платёжками и партнёрами. GraphQL — один адрес и схема типов: клиент одним запросом получает ровно те поля, что ему нужны, — удобно для сложных экранов и множества разных клиентов, но кэш, обработку ошибок и защиту от нагрузки приходится строить отдельно. Большинству сайтов, магазинов и интеграций хватает REST с описанием OpenAPI; GraphQL окупается, когда клиентов много и каждому нужны свои данные.

Коротко: что выбрать

Посмотрите, кто будет пользоваться API. Если это ваш сайт, мобильное приложение на несколько экранов, CRM, платёжная система или партнёр — REST: его знает каждый разработчик, поддерживает каждый инструмент, а ответы кэшируются по адресу. С описанием в OpenAPI другая команда подключается без созвонов и писем.

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

  • Сайт, магазин, интеграции — REST
  • Много клиентов, разные данные — GraphQL
  • В любом случае — описанный договор

REST и GraphQL: подробное сравнение

Четырнадцать критериев рядом — от формы ответа до мониторинга и защиты.

КритерийRESTGraphQL
Модель много адресов, по одному на ресурс один адрес и схема типов
Форма ответа решает сервер решает клиент
Лишние поля обычное дело нет, только запрошенное
Сложный экран несколько запросов один запрос
HTTP-кэш по адресу, CDN из коробки POST-запросы, нужен свой кэш
Ошибки HTTP-коды часто код 200 с ошибками внутри
Договор OpenAPI, пишется рядом сама схема
Изменения новые поля или версия /v2/ новые поля, старые @deprecated
Загрузка файлов просто обычно отдельный адрес REST
Защита от нагрузки лимиты на адрес лимиты на глубину и стоимость запроса
Мониторинг по адресу в любом журнале по имени операции, нужна настройка
Клиент любой HTTP-клиент, даже curl любой, но удобнее с библиотекой
Интеграции с CRM и платёжками стандарт, плюс вебхуки редко
Порог входа низкий выше

Какой подход под какой проект

Десять типичных проектов с рекомендацией и причиной.

ПроектБратьПочему
Сайт или магазин со своим интерфейсом REST несколько понятных адресов, кэш на CDN
Обмен с CRM или учётом REST другая сторона ждёт REST и вебхуки
Платежи REST платёжные системы работают через REST и подписанные вебхуки
Открытый API для партнёров REST подключается любым инструментом, описан в OpenAPI
Telegram-бот или Mini App REST хватает нескольких адресов
Мобильное приложение на несколько экранов REST проще, если экраны не сильно различаются
Веб, iOS, Android и портал партнёров GraphQL каждый клиент берёт свой срез одной схемы
Панель с множеством виджетов GraphQL один запрос вместо десятков
Headless CMS, где GraphQL уже есть GraphQL брать то, что даёт система
Файлы и большие выгрузки REST потоки и загрузки для него родные

Одна карточка товара, два API

Одни и те же данные — товар и отзывы о нём — через REST и GraphQL. Оба работали на одном тестовом сервере; ответы в комментариях скопированы из настоящих запросов.

REST: договор

Два адреса, описанные в OpenAPI 3.1; файл проходит проверку Redocly.

openapi.yaml
# REST: договор описан в OpenAPI — другая команда подключается по нему
openapi: 3.1.0
info:
  title: Shop API
  version: 1.0.0
servers:
  - url: https://shop.example.com
security: []   # открытый каталог, для чтения ключ не нужен
paths:
  /api/products/{sku}:
    get:
      operationId: getProduct
      summary: Один товар со всеми полями
      parameters:
        - { name: sku, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: Товар
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Product" }
        "404":
          description: Такого товара нет
  /api/products/{sku}/reviews:
    get:
      operationId: getProductReviews
      summary: Отзывы о товаре — отдельный запрос
      parameters:
        - { name: sku, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: Отзывы
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Review" }
        "404":
          description: Такого товара нет
components:
  schemas:
    Product:
      type: object
      required: [sku, title, price, stock, description]
      properties:
        sku: { type: string }
        title: { type: string }
        price: { type: integer }
        stock: { type: integer }
        description: { type: string }
    Review:
      type: object
      required: [author, rating]
      properties:
        author: { type: string }
        rating: { type: integer, minimum: 1, maximum: 5 }

REST: запросы

Карточке нужны два запроса, и первый приносит поля, которые страница не использует.

rest.sh
# REST: карточке товара с оценками нужны два запроса,
# а первый отдаёт все поля, даже если странице нужно только название
curl https://shop.example.com/api/products/A-100
# {"sku":"A-100","title":"Дубовый стол","price":24000,"stock":3,
#  "description":"Массив дуба, 160 × 90 см, масло с воском"}

curl https://shop.example.com/api/products/A-100/reviews
# [{"author":"Анна","rating":5},{"author":"Марк","rating":4}]

GraphQL: схема

Схема — одновременно договор и документация; отзывы — поле товара.

schema.graphql
# GraphQL: одна схема, один адрес — клиент сам выбирает поля
type Product {
  sku: String!
  title: String!
  price: Int!
  stock: Int!
  description: String!
  reviews: [Review!]!
}

type Review {
  author: String!
  rating: Int!
}

type Query {
  product(sku: String!): Product
}

GraphQL: запрос

Один запрос, только нужные поля. Ошибка про неизвестное поле пришла с HTTP-кодом 200.

query.graphql
# GraphQL: один POST-запрос на /graphql — клиент перечисляет ровно те поля, что ему нужны
query ProductCard {
  product(sku: "A-100") {
    title
    reviews {
      rating
    }
  }
}

# Ответ:
# {"data":{"product":{"title":"Дубовый стол","reviews":[{"rating":5},{"rating":4}]}}}

# Поле, которого нет в схеме, отклоняется ещё до выполнения:
# Cannot query field "color" on type "Product".

REST API, к которому легко подключиться

Большинство жалоб на REST — про плохо спроектированный API, а не про REST. Шесть правил, которые их снимают.

  1. 01

    OpenAPI с первого дня

    Описание — это договор: по нему строятся документация, проверки и клиентский код.

  2. 02

    Выбор полей

    ?fields=title,price снимает главный довод в пользу GraphQL — лишние данные.

  3. 03

    Связанные данные по запросу

    ?include=reviews возвращает товар вместе с отзывами одним ответом.

  4. 04

    Честные коды ответа

    404, 409, 422 и 429 вместо 200 с ошибкой внутри — мониторинг видит проблемы сам.

  5. 05

    Страницы и лимиты

    Списки всегда постранично, а свой лимит запросов клиент знает из заголовков.

  6. 06

    Вебхуки с подписью

    События уходят в другую систему сами, а подпись не даёт чужому подделать их.

Если GraphQL: что настроить с самого начала

GraphQL даёт клиенту много свободы. Эти настройки не дают ей обернуться против сервера.

  1. 01

    Лимиты глубины и стоимости

    Без них один вложенный запрос нагружает базу как тысячи обычных.

  2. 02

    Пакетная загрузка против N+1

    DataLoader собирает отзывы пятидесяти товаров в один запрос вместо пятидесяти.

  3. 03

    Сохранённые запросы

    Известные запросы идут по хешу — их можно кэшировать, а произвольные никто не пришлёт.

  4. 04

    Имена операций в журналах

    Все запросы идут на один адрес, и только по имени видно, какой из них медленный.

  5. 05

    Коды ошибок внутри

    Ошибки несут код в extensions, а мониторинг читает тело ответа, а не только статус.

  6. 06

    Права на поля

    Доступ проверяется в каждом резолвере — одна схема открыта каждому клиенту.

Частые ошибки при выборе

  1. GraphQL для одного сайта

    Схема, резолверы и лимиты ради одного клиента, которому хватило бы пяти адресов.

  2. REST без описания

    Другая команда изучает API методом проб и по переписке.

  3. Верить коду 200

    В GraphQL ответ с ошибками часто приходит с кодом 200 — мониторинг по статусу его пропускает.

  4. Открытый GraphQL без лимитов

    Одного составленного запроса хватает, чтобы остановить сервер.

  5. Адрес под каждый экран

    REST превращается в десятки особых адресов, хотя хватило бы выбора полей.

  6. Выбирать по моде

    На каком языке говорить API, решают партнёры, CRM и платёжная система.

Вопросы о REST и GraphQL

GraphQL вытесняет REST?

Нет: REST остаётся стандартом для интеграций и открытых API, GraphQL занимает своё место в продуктах с множеством клиентов.

Что быстрее?

GraphQL экономит запросы на сложных экранах, REST выигрывает на кэше. Скорость больше зависит от базы.

Можно ли совмещать?

Да, и часто: GraphQL для интерфейсов продукта, REST и вебхуки — для партнёров и платежей.

Что такое OpenAPI?

Стандартное описание REST API: адреса, параметры и ответы. По нему генерируются документация и клиенты.

Нужен ли для GraphQL особый клиент?

Нет, это обычный POST с JSON; библиотеки вроде Apollo или urql добавляют кэш и удобство.

Безопасен ли GraphQL?

Настолько, насколько он настроен: лимиты глубины, права на поля и сохранённые запросы обязательны.

А gRPC?

Это обмен между внутренними сервисами; браузерам и партнёрам всё равно отдают REST или GraphQL.

Форма

Обсудить
API

Строю REST API с описанием в OpenAPI и вебхуками с проверкой подписи — другая система подключается по документации. Расскажите о проекте — отвечу в течение рабочего дня.

Или пишите на [email protected]