GraphQL¶
GraphQL - спецификация языка запросов и среды их выполнения. Работает поверх разных транспортов: обычно HTTP, для подписок - WebSocket.
- один URL -
/graphql, вместо набора ресурсов - клиент сам решает, что получить - приходят только запрошенные поля. Это решает over-fetching (лишние данные) и under-fetching (данных не хватило, нужен ещё запрос)
- stateless, как REST - запросы (queries) и мутации (mutations) идут обычными HTTP-запросами
- stateful-часть - подписки (subscriptions) держат постоянное WebSocket-соединение и его состояние
Как выполняется запрос¶
Клиент отправляет запрос через HTTP POST:
POST /graphql HTTP/1.1
Content-Type: application/json
{
"query": "query { user(id: 1) { name email } }"
}
Сервер:
- Парсит запрос в AST (Abstract Syntax Tree).
- Валидирует его по схеме: такие типы и поля существуют, аргументы правильные.
- Исполняет: для каждого поля запускает свой резолвер. Резолвер берёт данные откуда угодно:
- из БД (SQL, MongoDB)
- из других API (REST, gRPC)
- из кэша (Redis)
- Собирает ответ в JSON ровно той формы, которую запросил клиент.
Схема и резолверы¶
Схема - контракт API: какие типы, поля и операции есть. Резолвер - функция, которая знает, откуда взять значение одного поля.
type Query {
user(id: ID!): User
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
const resolvers = {
Query: {
user: (parent, args, context) => {
return db.users.find(user => user.id === args.id); // данные из БД
}
},
User: {
posts: (user) => {
return db.posts.filter(post => post.authorId === user.id); // связанные данные
}
}
};
Операции¶
Query - чтение. Клиент точно указывает нужные поля и за один запрос получает связанные данные, например пользователя, его посты и комментарии к ним.
Mutation - изменение данных, аналог POST, PUT, PATCH, DELETE в REST. В ответе сразу приходят обновлённые поля.
Subscription - данные в реальном времени: уведомления, чаты. Единственный способ, которым сервер сам инициирует отправку данных клиенту:
- Клиент подключается к серверу через WebSocket и отправляет подписку.
- Сервер держит соединение и ждёт события, например новое сообщение в чате.
- Событие произошло - сервер отправляет данные всем подписанным клиентам.
Протокол поверх WebSocket - graphql-ws. Старый subscriptions-transport-ws больше не развивается.
Интроспекция - клиент сам получает схему API: типы, поля, документацию. На ней строятся автодокументация и IDE вроде GraphiQL.
Ограничения¶
- Нет встроенного HTTP-кэширования. В REST кэшируется ответ по URL, а в GraphQL всё идёт POST-запросами на
один
/graphql. Кэш настраивают на клиенте (Apollo Client) или через persisted queries с GET - Сложные запросы. Клиент может прислать запрос с глубокой вложенностью и положить сервер. Защита - лимиты глубины и сложности запроса
- Проблема N+1. Резолвер поля
postsвызывается для каждого пользователя отдельно, и на 100 пользователей выходит 101 запрос к БД. Решают батчингом через DataLoader. Та же проблема на уровне ORM - в N+1 и планы загрузки
Инструменты¶
| Сторона | Инструмент | Что это |
|---|---|---|
| Сервер | Apollo Server | Сервер на Node.js |
| Сервер | GraphQL Yoga | Упрощённый сервер |
| Сервер | Hasura | GraphQL поверх PostgreSQL |
| Клиент | Apollo Client | Клиент для React и Vue |
| Клиент | Relay | Клиент от Meta, оптимизирован под их стек |
| Клиент | URQL | Лёгкая альтернатива |