GraphQL
GraphQL
Основы
★ GraphQL — язык запросов к API и среда на сервере, которая эти запросы выполняет. Клиент в тексте запроса перечисляет нужные поля; сервер возвращает JSON ровно такой формы — без лишних данных.
В классическом REST у каждого ресурса свой URL (/users, /orders/42), а состав ответа задаёт сервер. В типичном GraphQL-приложении один HTTP-адрес (часто POST /graphql) обслуживает разные запросы. Сервер публикует схему типов — список сущностей и полей, по которому клиент и IDE подсказывают автодополнение.
Спецификация — graphql.org. Вводная на MDN — GraphQL.
GraphQL часто берут, когда:
- мобильному клиенту нужен короткий ответ без лишних полей
- один запрос собирает данные из нескольких источников (меньше round-trip к серверу)
- много разных экранов и у каждого свой набор полей
- слой BFF (Backend For Frontend) склеивает микросервисы
Контракты REST — JSON Schema и OpenAPI. Обмен данными — JSON, интеграции.
Схема (SDL)
SDL (Schema Definition Language) — язык описания типов GraphQL. Схема похожа на контракт OpenAPI, но мыслит объектами и связями, а URL.
type Query {
user(id: ID!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
order(id: ID!): Order
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
cancelOrder(id: ID!): Order!
}
type Subscription {
orderStatusChanged(orderId: ID!): Order!
}
type User {
id: ID!
email: String!
name: String
orders(first: Int = 10, after: String): OrderConnection!
createdAt: String!
}
type Order {
id: ID!
total: Float!
status: OrderStatus!
createdAt: String!
items: [OrderItem!]!
}
type OrderItem {
productId: ID!
title: String!
quantity: Int!
price: Float!
}
enum OrderStatus {
PENDING
PAID
SHIPPED
CANCELLED
}
input CreateOrderInput {
userId: ID!
items: [OrderItemInput!]!
}
input OrderItemInput {
productId: ID!
quantity: Int!
}
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
}
type OrderEdge {
cursor: String!
node: Order!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Обозначения в типах:
!после типа — поле обязательно, не может бытьnull[User!]!— список пользователей безnullвнутри; сам список тоже неnullID,String,Int,Float,Boolean— встроенные скалярные типыQuery— точка входа для чтенияMutation— для измененийSubscription— для потоковых обновленийinput— типы только для аргументов мутацийenum— фиксированный набор строковых значений
Резолвер — функция на сервере, которая по полю схемы достаёт данные (часто из SQL или ORM).
Запрос query
Клиент запрашивает только перечисленные поля:
query GetUserWithOrders {
user(id: "42") {
email
name
orders(first: 3) {
edges {
node {
id
total
status
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Ответ в JSON той же вложенности:
{
"data": {
"user": {
"email": "ivan@example.com",
"name": "Иван",
"orders": {
"edges": [
{
"node": {
"id": "5001",
"total": 1990.0,
"status": "PAID"
}
}
],
"pageInfo": {
"hasNextPage": true,
"endCursor": "YXJyYXljb25uZWN0aW9uOjM="
}
}
}
}
}
Запрос уходит HTTP-методом POST с телом:
{
"query": "query GetUserWithOrders { user(id: \"42\") { email } }",
"operationName": "GetUserWithOrders"
}
Некоторые серверы разрешают GET с query в query-string для кэширования — редко на проде из-за лимита длины URL.
Переменные
Жёстко прошитые литералы в строке запроса неудобны для приложения. Variables передают снаружи:
query GetUser($userId: ID!, $orderLimit: Int = 5) {
user(id: $userId) {
email
orders(first: $orderLimit) {
edges {
node {
id
total
}
}
}
}
}
Тело HTTP:
{
"query": "query GetUser($userId: ID!, $orderLimit: Int = 5) { ... }",
"operationName": "GetUser",
"variables": {
"userId": "42",
"orderLimit": 3
}
}
Правила переменных:
- имя в запросе с
$:$userId - тип обязателен:
$userId: ID! - значение по умолчанию только у необязательных:
$limit: Int = 10 variablesв JSON — обычный объект, не строка
Ошибка Variable $id was not provided — забыли ключ в variables или передали null для ID!.
Фрагменты
Fragment — переиспользуемый набор полей для одного типа:
fragment OrderSummary on Order {
id
total
status
createdAt
}
query UserOrders($id: ID!) {
user(id: $id) {
email
orders(first: 10) {
edges {
node {
...OrderSummary
items {
title
quantity
}
}
}
}
}
}
Inline fragment — условные поля по типу интерфейса или union:
query Search($text: String!) {
search(text: $text) {
... on User {
email
}
... on Product {
title
price
}
}
}
Фрагменты уменьшают дублирование в клиентском коде (Apollo Client кэширует по фрагментам).
Псевдонимы (aliases)
Если нужно дважды вызвать одно поле с разными аргументами:
query CompareUsers {
alice: user(id: "1") {
email
name
}
bob: user(id: "2") {
email
name
}
}
Ответ:
{
"data": {
"alice": { "email": "alice@example.com", "name": "Алиса" },
"bob": { "email": "bob@example.com", "name": "Боб" }
}
}
Без alias второй user перезаписал бы первый в объекте data.
Мутации
Mutation меняет данные — аналог POST или PATCH в REST:
mutation CreateOrder($input: CreateOrderInput!) {
createOrder(input: $input) {
id
total
status
items {
productId
quantity
}
}
}
Переменные:
{
"input": {
"userId": "42",
"items": [
{ "productId": "99", "quantity": 2 }
]
}
}
Рекомендации:
- мутации называют глаголом:
createOrder,updateProfile - возвращайте изменённый объект — клиент обновит кэш без второго query
- для идемпотентности используйте клиентский
idempotencyKeyвinput, если сервер поддерживает
Интроспекция
GraphQL-схема сама доступна через GraphQL. Запрос introspection возвращает список типов, полей и аргументов.
query IntrospectOrderType {
__type(name: "Order") {
name
fields {
name
type {
name
kind
}
}
}
}
Зачем это нужно:
- GraphiQL и Apollo Studio рисуют документацию из introspection
- codegen генерирует TypeScript-типы из схемы
- IDE подсказывает поля при написании query
На продакшене introspection часто отключают — схема раскрывает внутреннюю структуру. На staging оставляют для разработчиков.
Системные поля __schema и __type — часть спецификации, не пишите их в публичной SDL вручную.
Проблема N+1 и DataLoader
Наивные резолверы вызывают базу на каждое поле каждого объекта. Один GraphQL-запрос списка пользователей с заказами превращается в сотни SQL-запросов — проблема N+1.
DataLoader — библиотека, которая батчит и кэширует загрузку за один tick event loop:
import DataLoader from "dataloader";
const orderLoader = new DataLoader(async (userIds) => {
const orders = await db.orders.findByUserIds(userIds);
const grouped = groupByUserId(orders);
return userIds.map((id) => grouped[id] ?? []);
});
// В резолвере User.orders:
orders: (user) => orderLoader.load(user.id),
Один вызов load для многих пользователей → один SQL WHERE user_id IN (...).
Альтернативы:
- JOIN в одном запросе на уровне root resolver
- lookahead — анализ AST запроса до выполнения
- ORM с eager loading, если GraphQL-запрос предсказуем
Симптом N+1 в логах — десятки одинаковых SELECT подряд при одном HTTP-запросе к /graphql.
Аутентификация (JWT)
GraphQL не задаёт свой способ auth. Обычно используют тот же JWT в заголовке, что и в OpenAPI bearerAuth:
POST /graphql HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
Сервер на каждый запрос:
- проверяет токен
- кладёт
userIdв context - резолверы читают context и фильтруют данные
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
const token = req.headers.authorization?.replace("Bearer ", "");
const user = await verifyJwt(token);
return { user, db };
},
});
Ошибки auth:
- HTTP 401 — нет токена или токен невалиден (до выполнения GraphQL)
- GraphQL
errorsсextensions.code: UNAUTHENTICATED— токен есть, но прав на поле нет
Для публичных и приватных полей в одной схеме — проверка прав в резолвере, не только на HTTP-уровне.
Пагинация — курсоры Relay
Offset (limit / offset) ломается при частых вставках: страница 2 после добавления строки показывает дубликаты. Cursor-based пагинация стабильнее.
Спецификация Relay Cursor Connections:
query OrdersPage($userId: ID!, $after: String) {
user(id: $userId) {
orders(first: 10, after: $after) {
edges {
cursor
node {
id
total
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
first— сколько элементов взятьafter— курсор с прошлой страницы (endCursor)edges[].cursor— непрозрачная строка (часто base64)pageInfo.hasNextPage— есть ли ещё данные
Сравнение с OpenAPI offset-пагинацией:
| Подход | GraphQL Relay | REST query page/pageSize |
|---|---|---|
| Стабильность при вставках | лучше | хуже на больших таблицах |
| Сложность клиента | выше | ниже |
| Кэш CDN | сложнее | проще для GET |
Подписки (subscriptions)
Subscription — долгоживущий канал обновлений (статус заказа, чат, уведомления). Транспорт — чаще WebSocket, реже SSE.
Схема:
type Subscription {
orderStatusChanged(orderId: ID!): Order!
}
Клиент:
subscription WatchOrder($id: ID!) {
orderStatusChanged(orderId: $id) {
id
status
}
}
Сервер пушит новые объекты Order при смене статуса.
Ограничения:
- сложнее масштабировать за одним инстансом (нужен pub/sub — Redis, Kafka)
- не все хостинги разрешают WebSocket
- для редких опросов хватает polling REST
Обзор достаточен на старте; углубляться после уверенных query и mutation.
Федерация (кратко)
В больших компаниях схему делят между командами. Apollo Federation (и аналоги) позволяют собрать один публичный GraphQL из нескольких микросервисов:
- сервис
usersобъявляетtype User @key(fields: "id") - сервис
ordersрасширяетextend type User @key(fields: "id") { orders: [Order!]! } - gateway маршрутизирует части запроса в нужный сервис
Связь с микросервисами — федерация это способ склеить BFF без монолитной схемы в одном репозитории.
Минусы: операционная сложность, отладка запроса через несколько сетевых прыжков.
Обработка ошибок
Ответ GraphQL всегда HTTP 200 часто, даже при ошибках — проверяйте и errors, и data.
Частичный успех:
{
"data": {
"user": {
"email": "ivan@example.com",
"orders": null
}
},
"errors": [
{
"message": "Access denied to orders",
"path": ["user", "orders"],
"extensions": {
"code": "FORBIDDEN"
}
}
]
}
Типы ситуаций:
| Уровень | Пример | Что делать клиенту |
|---|---|---|
| HTTP 4xx/5xx | 401, 502 | Повтор auth / показать "сервер недоступен" |
| GraphQL errors | поле не найдено в схеме | Исправить запрос |
| GraphQL errors + data | одно поле упало | Показать остальное, пометить ошибку |
| Бизнес-ошибка в mutation | userError в payload | Обработать как в REST 422 |
В мутациях паттерн union payload:
union CreateOrderResult = Order | ValidationError
type ValidationError {
message: String!
field: String
}
type Mutation {
createOrder(input: CreateOrderInput!): CreateOrderResult!
}
Клиент через inline fragment выбирает ветку — типобезопаснее, чем только errors в массиве.
Лимиты глубины и rate limiting
Открытый GraphQL без лимитов уязвим к тяжёлым вложенным запросам:
query Attack {
users {
friends {
friends {
friends {
email
}
}
}
}
}
Защита:
- max depth — глубина вложенности полей (например 7)
- complexity score — у каждого поля вес, сумма не выше порога
- rate limiting по IP или токену (как HTTP 429 в OpenAPI)
- timeout на выполнение запроса
- persisted queries — клиент шлёт только hash известного запроса
Настройки задают на gateway или в Apollo Server / Hot Chocolate / Strawberry.
Apollo Server, Hot Chocolate, Strawberry
| Реализация | Стек | Особенности |
|---|---|---|
| Apollo Server | Node.js | де-факто стандарт, Federation, Studio |
| GraphQL Yoga | Node, универсальный | лёгкий, современный middleware |
| Hot Chocolate | .NET | интеграция ASP.NET, проекции, paging |
| Strawberry | Python | type hints, dataclasses, FastAPI |
| Graphene | Python | старше Strawberry, ещё встречается |
Минимальный Strawberry + FastAPI:
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
@strawberry.type
class User:
id: strawberry.ID
email: str
@strawberry.type
class Query:
@strawberry.field
def user(self, id: strawberry.ID) -> User | None:
return User(id=id, email="demo@example.com")
schema = strawberry.Schema(query=Query)
app = FastAPI()
app.include_router(GraphQLRouter(schema), prefix="/graphql")
Hot Chocolate в ASP.NET:
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddProjections()
.AddFiltering();
Выбор сервера обычно следует за стеком backend; контракт SDL остаётся переносимым между языками.
Тестирование с GraphiQL
GraphiQL — IDE в браузере для запросов к живому endpoint:
- автодополнение из introspection
- панель Variables (JSON)
- история запросов
- Docs explorer по типам
Где найти:
/graphqlв Apollo Server и Strawberry по умолчанию на dev- расширение GraphiQL в VS Code
- Postman, Insomnia — вкладка GraphQL
Чек-лист теста вручную:
- выполнить query с variables
- mutation с невалидным
input— ожидатьerrorsилиValidationError - запрос без токена на защищённое поле — 401 или FORBIDDEN
- глубокий вложенный query — отклонение по complexity
Автотесты — HTTP-клиент + assert на data и errors (pytest, Jest). Контракт SDL можно проверять graphql-schema-linter.
Связь с отладкой — логируйте operationName и время резолверов.
Паттерн BFF
BFF (Backend For Frontend) — тонкий backend под конкретный клиент (iOS, web, admin). GraphQL хорошо ложится на BFF:
- мобильному клиенту — короткий query без лишних полей admin-панели
- BFF агрегирует три REST-вызова в один GraphQL-запрос
- внутренние сервисы остаются на OpenAPI, наружу только GraphQL
Риск: BFF превращается в монолит — держите резолверы тонкими, бизнес-логику в доменных сервисах.
Сравнение с REST и OpenAPI
| Критерий | REST + OpenAPI | GraphQL |
|---|---|---|
| Адреса | много URL | обычно один /graphql |
| Состав ответа | задаёт сервер (DTO) | выбирает клиент в запросе |
| Кэш браузера и CDN | проще по URL и GET | сложнее; нужен клиент с нормализацией |
| Версии API | /v1, /v2 | эволюция схемы, @deprecated |
| Документация | Swagger UI, Redoc | GraphiQL, Apollo Studio |
| Нагрузка на сервер | предсказуемые endpoint | риск тяжёлых запросов — нужны лимиты |
| Загрузка файлов | отдельный multipart endpoint | мутация + upload scalar (сложнее) |
| Пагинация | page, offset, cursor в query | Relay connections, единый стиль в схеме |
| Ошибки | HTTP-код + тело | HTTP + массив errors + частичный data |
| Типизация клиента | openapi-generator | graphql-codegen |
| Подписки | WebSocket отдельно | Subscription в схеме |
| Сборка из микросервисов | API Gateway + REST | Federation, BFF |
| Обучение команды | ниже порог | выше порог (SDL, резолверы, N+1) |
| Идемпотентность | PUT, DELETE по смыслу HTTP | договорённость в mutation design |
Когда проще REST:
- публичный CRUD с кэшированием CDN
- загрузка бинарных файлов отдельными endpoint
- команда уже стандартизировалась на OpenAPI
- партнёры ждут простые curl-примеры по URL
Когда уместен GraphQL:
- много клиентских экранов с разным набором полей
- слой BFF над микросервисами
- агрегация данных за один round-trip
- мобильный трафик дорог — экономия байт в ответе
Оба подхода могут сосуществовать: публичный REST по OpenAPI, внутренний GraphQL для своих приложений.
Полный пример сценария
Сценарий: авторизованный пользователь создаёт заказ и смотрит список.
Шаг 1 — query текущего пользователя
query Me {
me {
id
email
orders(first: 5) {
edges {
node {
id
status
total
}
}
}
}
}
Шаг 2 — mutation с variables
mutation PlaceOrder($input: CreateOrderInput!) {
createOrder(input: $input) {
... on Order {
id
status
total
}
... on ValidationError {
message
field
}
}
}
Шаг 3 — subscription (опционально)
subscription OnOrderPaid($id: ID!) {
orderStatusChanged(orderId: $id) {
id
status
}
}
Цепочка данных:
Работа с SQL и ORM
Резолверы редко пишут SQL вручную в каждом поле — используют ORM:
# Псевдокод Strawberry + SQLAlchemy
def resolve_orders(self, user: User, info, first: int, after: str | None):
qs = db.session.query(Order).filter(Order.user_id == user.id)
if after:
qs = qs.filter(Order.cursor > decode_cursor(after))
return qs.limit(first).all()
Правила:
- один root query — один осмысленный запрос к БД где возможно
- вложенные поля — DataLoader или JOIN
- индексы на
user_id, поля курсора - N+1 смотрите в логах SQL
GraphQL не заменяет SQL — он слой над источником данных.
Типичные ошибки
| Симптом | Причина |
|---|---|
Cannot query field X | Поля нет в схеме или опечатка в имени |
Variable $id was not provided | Не передали обязательный аргумент |
Variable $id got invalid value | Тип в JSON не совпадает с SDL ("42" для Int без кавычек в variables) |
| Сотни SQL-запросов на один GraphQL-запрос | Резолвер на каждое поле без batch (DataLoader) |
| Сервер падает от одного запроса | Нет лимита глубины и сложности query |
Пустой data и HTTP 401 | Токен не передан — проверьте заголовок |
| Дубли на второй странице offset | Перешли на cursor pagination |
| Introspection пустая на prod | Отключена политикой — используйте staging schema |
Когда GraphQL не подходит
- публичное API для партнёров без SDK — OpenAPI и curl проще
- жёсткое CDN-кэширование GET-ресурсов
- команда без опыта и жёсткие сроки — REST быстрее стартовать
- весь backend — один простой CRUD без агрегации
- критична минимальная поверхность атаки — GraphQL introspection и гибкие запросы требуют дисциплины
Директива @deprecated
Схема GraphQL эволюционирует без /v2 в URL. Устаревшие поля помечают @deprecated:
type User {
id: ID!
email: String!
login: String @deprecated(reason: "Используйте email как идентификатор")
}
GraphiQL зачёркивает поле и показывает reason. Клиенты codegen получают предупреждение при компиляции. Удаление поля — только после метрик: кто ещё запрашивает login.
Сравнение с версиями в OpenAPI: там часто дублируют path /v1/users и /v2/users. В GraphQL один тип User живёт дольше, меняется постепенно.
Клиентский кэш (Apollo Client)
Сервер отдаёт дерево JSON; клиент нормализует его в кэш по id:
// Упрощённая идея Apollo InMemoryCache
{
"User:42": { id: "42", email: "ivan@example.com" },
"Order:5001": { id: "5001", total: 1990, __typename: "Order" }
}
Повторный query с пересекающимися полями не дублирует данные в памяти. Мутация createOrder обновляет кэш через refetchQueries или update функцию.
Почему REST проще кэшировать на CDN:
- GET
/products/42— один URL, один ответ - POST
/graphqlс разным телом query — CDN не знает, что кэшировать без дополнительной настройки
Для публичного read-only контента GraphQL реже выигрывает у REST с CDN.
Настройка лимитов — пример Apollo Server
import depthLimit from "graphql-depth-limit";
import { createComplexityLimitRule } from "graphql-validation-complexity";
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(7),
createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 2,
listFactor: 10,
}),
],
});
Порог подбирают по профилю реальных запросов мобильного и web-клиента. Слишком низкий — легитимные экраны ломаются; слишком высокий — окно для DoS остаётся.
Логируйте отклонённые запросы с operationName — так видно, какой экран нужно упростить.
JWT — полный цикл на клиенте
async function graphqlFetch(query, variables) {
const token = localStorage.getItem("accessToken");
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ query, variables }),
});
if (response.status === 401) {
// refresh token flow или редирект на login
throw new Error("unauthorized");
}
const json = await response.json();
if (json.errors?.length) {
console.error(json.errors);
}
return json;
}
Refresh token обычно остаётся на отдельном REST endpoint — OpenAPI удобнее описывает POST /auth/refresh, чем смешивать с GraphQL mutation.
Тест в pytest — пример
import httpx
QUERY = """
query GetUser($id: ID!) {
user(id: $id) { email }
}
"""
def test_get_user_returns_email(graphql_url, auth_headers):
payload = {
"query": QUERY,
"variables": {"id": "42"},
}
r = httpx.post(graphql_url, json=payload, headers=auth_headers)
assert r.status_code == 200
body = r.json()
assert "errors" not in body or body["errors"] is None
assert body["data"]["user"]["email"] == "ivan@example.com"
Для mutation добавьте проверку побочного эффекта в SQL — строка заказа появилась в таблице.
Federation — схема из двух сервисов
Сервис users:
type User @key(fields: "id") {
id: ID!
email: String!
}
Сервис orders:
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order!]!
}
type Order {
id: ID!
total: Float!
}
Gateway объединяет типы. Клиент видит один GraphQL, запрос:
query {
user(id: "1") {
email
orders { id total }
}
}
Gateway запрашивает email у users, orders у orders. Отладка сложнее монолита — trace id на весь запрос обязателен.
Сравнение инструментов отладки
| Инструмент | Сильная сторона |
|---|---|
| GraphiQL | быстрый ручной query на dev |
| Apollo Studio | схема, метрики, trace |
| Postman | коллекции для QA, окружения |
| graphql-codegen | типы TypeScript из SDL |
| SpectaQL | статическая HTML-документация из схемы |
Документация REST — Swagger UI. Обе стороны должны иметь один источник правды — SDL или OpenAPI в git.
Интеграция с микросервисной архитектурой
Типичные роли:
- Публичный REST — партнёры, webhook, простые интеграции (интеграции)
- Внутренний GraphQL BFF — свои приложения
- gRPC / очереди — между сервисами без HTTP JSON
Не обязательно выбирать один стиль на всю компанию — выбирают на границе системы.
Практикум — упражнения
Уровень 1 — запросы
- По SDL выше напишите query только с
user.emailиorders.edges.node.id. - Перепишите с variables
$userIdи$first. - Добавьте alias
primaryиsecondaryдля двух вызововuserс разными id.
Уровень 2 — фрагменты
- Вынесите поля заказа в
fragment OrderCard on Order. - Используйте фрагмент в query и mutation ответа
createOrder.
Уровень 3 — пагинация
- Выполните два последовательных query с
after: endCursorиз первого ответа. - Объясните, почему при offset это могло бы дать дубликат.
Уровень 4 — auth и ошибки
- Отправьте запрос к
meбез заголовка Authorization — зафиксируйте HTTP-код и тело. - Отправьте mutation с пустым
items— получитеValidationErrorилиerrors.
Уровень 5 — производительность
- Включите лог SQL и выполните query
users { orders { items { title } } }без DataLoader — посчитайте запросы. - Добавьте DataLoader и сравните число SQL.
Уровень 6 — сравнение с REST
- Опишите тот же сценарий
createOrderв фрагменте OpenAPI — сколько HTTP-вызовов понадобилось бы в REST для экрана checkout. - Составьте таблицу полей, которые лишние для мобильного клиента в REST DTO, но не придут в GraphQL query.
Разбор — в паре с JSON Schema практикумом.
Шпаргалка
| Задача | Механизм GraphQL |
|---|---|
| Меньше полей в ответе | query с перечислением полей |
| Параметры с клиента | variables |
| Переиспользование полей | fragments |
| Два вызова одного поля | aliases |
| Документация схемы | introspection + GraphiQL |
| Много SQL на один запрос | DataLoader |
| JWT | заголовок Authorization |
| Стабильные страницы списка | Relay cursors |
| Live обновления | subscriptions |
| Несколько микросервисов | federation или BFF |
| Лимит тяжёлых query | depth / complexity / rate limit |
Связанные материалы
- JSON Schema и OpenAPI
- JSON
- SQL — о разделе
- ORM и работа с данными
- Микросервисы и интеграция
- Основы интеграционного взаимодействия
- Разработка и отладка