GraphQL
| Разработчик | Facebook (Meta), с 2018 — GraphQL Foundation |
|---|---|
| Внутреннее использование | 2012 |
| Открыт | 2015 |
| Тип | язык запросов и спецификация API |
| Транспорт | обычно HTTP POST, подписки — WebSocket или SSE |
| Формат ответа | JSON |
| Реализации | Apollo, GraphQL Yoga, Hasura, Strawberry, gqlgen, Juniper |
GraphQL — язык запросов к API и спецификация того, как сервер должен их выполнять. В отличие от REST, где у каждого ресурса свой адрес и фиксированный формат ответа, в GraphQL обычно один адрес (/graphql), а клиент в запросе перечисляет именно те поля и связи, которые ему нужны. Сервер описывает свои данные схемой со строгими типами, проверяет запрос по ней и возвращает ответ в формате JSON той же формы, что и запрос.
История
Facebook начала использовать GraphQL в 2012 году для мобильных приложений: лента новостей требовала данных из множества источников, а REST-запросы на медленных мобильных сетях оказывались то избыточными, то недостаточными.[1] В 2015 году спецификацию и эталонную реализацию на JavaScript опубликовали, а в 2018 году проект передали независимому фонду GraphQL Foundation при Linux Foundation. Публичные GraphQL API предоставляют GitHub, Shopify, GitLab и многие другие.[6]
Схема
Схема пишется на языке SDL (Schema Definition Language):[2]
type User {
id: ID!
name: String!
posts(first: Int = 10): [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
}
type Mutation {
createPost(title: String!): Post!
}
! означает «не может быть null», [Post!]! — непустой список постов. Три корневых типа задают виды операций: Query — чтение, Mutation — изменение, Subscription — подписка на события в реальном времени.
Запросы
query {
user(id: "42") {
name
posts(first: 3) {
title
}
}
}
Ответ повторяет структуру запроса:
{"data": {"user": {"name": "Анна", "posts": [{"title": "Первый"}, {"title": "Второй"}]}}}
Так решаются две проблемы REST: over-fetching (ответ содержит лишние поля) и under-fetching (нужно несколько запросов, чтобы собрать связанные данные). Поддерживаются переменные, фрагменты для повторного использования наборов полей, псевдонимы и директивы @include/@skip. Схема доступна через интроспекцию, поэтому редакторы вроде GraphiQL подсказывают поля, а генераторы создают типы для TypeScript и других языков.
Как работает сервер
Каждому полю схемы соответствует функция-резолвер, получающая родительский объект и аргументы. Сервер обходит запрос и вызывает резолверы, собирая ответ. Ошибки возвращаются в массиве errors рядом с частичными данными, а HTTP-код при этом часто остаётся 200 — непривычно для тех, кто ждёт стандартных кодов состояния.[3]
Наивные резолверы порождают проблему N+1: запрос 50 постов с авторами выполнит 1 запрос за постами и 50 — за авторами. Её решают пакетной загрузкой (DataLoader), которая собирает ключи за один такт и делает один запрос к базе.
Сравнение с REST
| REST | GraphQL | |
|---|---|---|
| Адреса | много ресурсов | обычно один |
| Форма ответа | задаёт сервер | задаёт клиент |
| Типизация | по желанию (OpenAPI) | обязательная схема |
| HTTP-кэширование | естественное (GET, ETag) | сложнее, нужен кэш на клиенте |
| Версионирование | /v1, /v2 |
эволюция схемы, @deprecated |
| Загрузка файлов | просто | через расширения |
GraphQL выигрывает в приложениях с разнообразными клиентами и сложными связанными данными. REST проще для публичных API, кэширования через CDN и простых CRUD-сервисов. Ещё одна альтернатива для внутренних сервисов — gRPC.
Безопасность и производительность
Гибкость запросов открывает возможности для злоупотреблений: глубоко вложенный запрос может перегрузить сервер.[4] Обычные меры:
- ограничение глубины и стоимости запроса;
- persisted queries — сервер выполняет только заранее зарегистрированные запросы по хешу;
- отключение интроспекции в продакшене для закрытых API;
- проверка прав доступа в каждом резолвере, а не только на входе (см. Аутентификация и авторизация);
- ограничение частоты запросов по стоимости, а не по их количеству.
Экосистема
- Сервер: Apollo Server и GraphQL Yoga (Node.js), Strawberry и Graphene (Python), gqlgen (Go), Juniper и async-graphql (Rust), Hot Chocolate (.NET).
- Клиент: Apollo Client, Relay, urql — с нормализованным кэшем.
- Готовые API над базами: Hasura и PostGraphile генерируют GraphQL из схемы PostgreSQL.
- Федерация: Apollo Federation объединяет схемы нескольких сервисов в один граф.[5]