GraphQL vs REST: over-fetching и under-fetching

Главная боль REST — клиент получает либо слишком много данных (over-fetching), либо слишком мало и вынужден делать кучу запросов (under-fetching). GraphQL устраняет обе.

«REST отдаёт то, что придумал бэкенд. GraphQL отдаёт то, что попросил фронтенд». В этом сдвиге власти — половина всей разницы.

Представь экран профиля в мобильном приложении: нужно показать имя пользователя и заголовки его последних постов. На REST это часто выглядит так: сначала идёшь на /users/42 и получаешь весь объект пользователя (имя, email, дата рождения, адрес, настройки — всё, хотя нужно только имя). Это over-fetching: приехало много лишнего, особенно больно на мобильном интернете. Затем выясняется, что постов в ответе нет, и ты идёшь на /users/42/posts — это уже второй запрос. Если на экране ещё и аватар автора каждого поста, появляется третий, четвёртый круг. Это under-fetching: один эндпоинт не дал всего нужного, и клиент добирает данные дополнительными запросами.

GraphQL решает обе проблемы одним приёмом: клиент описывает точную форму данных в одном запросе, а сервер собирает связанное дерево за один заход. Никаких лишних полей и никаких лишних кругов по сети.

Наглядное сравнение

REST (over-fetch + under-fetch)        GraphQL (ровно нужное)
-----------------------------------    ----------------------------
GET /users/42                          POST /graphql
  -> { name, email, bday,                query {
       address, settings, ... }           user(id:"42") {
         (90% полей не нужны!)               name
GET /users/42/posts                          posts(last:2){ title }
  -> [ {...}, {...} ]                       }
         (ещё один круг по сети)          }
GET /posts/1/author ...                  -> один ответ, ровно поля
         (N+1 кругов)

По разным замерам GraphQL сокращает размер ответа на 30–50% именно за счёт отсутствия over-fetching — для мобильных и edge-сценариев это заметно.

Как работает под капотом

В REST маршрутизация идёт по URL: путь + HTTP-метод однозначно выбирают обработчик, который возвращает заранее заданную структуру. В GraphQL URL всегда один (/graphql), а «что вернуть» определяет тело запроса. Сервер разбирает запрос, сверяет со схемой и вызывает резолверы только для запрошенных полей. Поэтому два разных клиента, бьющие в один и тот же эндпоинт, могут получать совершенно разные по форме ответы.

Смоделируем оба подхода на чистом JS. «REST-эндпоинт» всегда отдаёт весь объект, «GraphQL» — только запрошенное:

const db = {
  name: "Аня", email: "[email protected]", bday: "2006-05-01",
  address: "Казань", settings: { theme: "dark" }
};

// REST: всегда весь объект (over-fetching)
function restUser() { return db; }

// GraphQL: только выбранные поля
function gqlUser(fields) {
  return fields.reduce((acc, f) => (acc[f] = db[f], acc), {});
}

console.log("REST  :", restUser());
console.log("GraphQL:", gqlUser(["name"]));

Попробуй сам ▶ — сравни объём двух ответов. REST тащит всё, GraphQL — один ключ.

Честное сравнение: где что лучше

КритерийRESTGraphQL
ЭндпоинтыМного (по ресурсам)Один
Выбор полейФиксирован серверомЗадаёт клиент
Связанные данныеЧасто N+1 круговЗа один запрос
HTTP-кэшИз коробки (GET+URL)Сложнее (POST, один URL)
Кривая входаНизкаяВыше (схема, резолверы)

Частые ошибки

  • «REST устарел, всё переписываем на GraphQL». Нет. По данным на 2024 год около 83% публичных API всё ещё на REST. Для простых, хорошо кэшируемых, публичных API REST часто удобнее.
  • Забыть про HTTP-кэширование. REST бесплатно кэшируется на уровне CDN по URL. В GraphQL запросы обычно POST в один URL — приходится думать о кэше на клиенте и о persisted queries.
  • Считать, что GraphQL автоматически быстрее. Меньше трафика — да, но без DataLoader легко получить ту же N+1, только внутри сервера.

Best practices

  • Выбирай GraphQL, когда у тебя много разных клиентов (web, mobile, партнёры) с разными потребностями в данных и много связанных сущностей.
  • Оставляй REST для простых CRUD-сервисов, файловых загрузок и публичных API, где важно агрессивное HTTP-кэширование.
  • Их можно совмещать: GraphQL-шлюз поверх существующих REST-сервисов — частый и рабочий паттерн миграции.

Итоги

REST страдает от over-fetching (лишние поля) и under-fetching (лишние запросы), потому что форму ответа диктует сервер. GraphQL отдаёт власть клиенту: один эндпоинт, точная выборка полей, связанные данные за один запрос. Зато REST проще и лучше дружит с HTTP-кэшем. Выбор зависит от задачи, а не от моды.

Проверьте себя
1. Что такое over-fetching в REST?
AКлиент делает слишком много запросов
BСервер возвращает больше полей, чем нужно клиенту
CСервер падает под нагрузкой
DКлиент кэширует устаревшие данные
2. В каком сценарии REST обычно предпочтительнее GraphQL?
AМного разных клиентов с разными нуждами
BГлубоко связанные данные за один запрос
CПростой публичный API с агрессивным HTTP-кэшированием по URL
DГибкая выборка полей клиентом