Это руководство по GraphQL. Из него вы узнаете базовую теорию, а также научитесь писать простые запросы с помощью GraphQL.
- Что такое GraphQL: теоретический ликбез
- Query: запросы в GraphQL
- Mutation: мутации в GraphQL
- Subscription: подписки в GraphQL
- Как работать с сервером GraphQL
- REST vs. GraphQL
- Заключение
Что такое GraphQL: теоретический ликбез
Прежде чем рассматривать GraphQL, давайте уделим внимание исторической базе. Что такое SQL — structured query language или язык структурированных запросов?
SQL — декларативный язык программирования, который применяется для создания, изменения и управления данными в базах данных. Этот язык поддерживает четыре базовых оператора запросов: SELECT, INSERT, UPDATE и DELETE. С помощью SQL мы можем запросить из базы данных (БД) именно то, что нам необходимо.
- Освойте азы современных языков программирования
- Изучите работу с Git и командной строкой
- Выберите себе профессию или улучшите навыки
Например, когда необходимо «достать» из БД всех пользователей с именем Maria, это можно сделать с помощью запроса:
SELECT * FROM USERS WHERE FirstName = "Maria"
Решить эту задачу с помощью REST можно несколькими способами:
- Определить endpoint на сервере, который будет отдавать из базы данных пользователей с
fname
Maria. - Определить общий endpoint для получения всех пользователей и фильтровать полученный список на стороне клиента.
users.filter(user => user.fname === "Maria");
При этом в каждом из вариантов есть свои минусы. Первый подход нельзя масштабировать, так как нет возможности создать endpoint для каждого пользователя. Если мы используем второй подход, повышается нагрузка на сеть, а также появляется необходимость в постобработке на стороне клиента.
А теперь представьте инструмент, который объединяет возможности SQL и REST на стороне клиента. Вы уже догадались, что он называется GraphQL. Этот инструмент берёт идеи, разработанные для манипуляции данными в БД, и использует их в вебе. Поэтому с помощью одного запроса GraphQL можно получить сразу все необходимые данные.
Ниже представлена информация, необходимая для начала работы с GraphQL.
Query: запросы в GraphQL
С помощью запросов GraphQL получает необходимые данные с сервера. Тип запроса Query в GraphQL — аналог GET в REST. Запросы — строки, которые отправляются в теле HTTP POST-запроса.
Примечание — Обратите внимание, все типы запросов в GraphQL отправляются через POST.
Query описывает данные, которые необходимо получить с сервера. Например, с помощью кода ниже можно получить fname
и age
всех пользователей в базе данных.
query {
users {
fname
age
}
}
В ответ на этот запрос сервер присылает данные в формате JSON. Структура ответа соответствует структуре запроса.
data : {
users [
{
"fname": "Joe",
"age": 23
},
{
"fname": "Betty",
"age": 29
}
]
}
Успешные операции возвращают JSON с ключом "data" и с ключом "error", а неуспешные возвращают JSON с ключом и сообщением об ошибке. Благодаря этому удобно обрабатывать ошибки на стороне клиента.
Mutation: мутации в GraphQL
Mutation — ещё один root types. С его помощью можно добавлять данные в БД. Mutation — аналог POST и PUT в REST. Ниже пример кода.
mutation createUser{
addUser(fname: "Richie", age: 22) {
id
}
}
Здесь создаётся мутация createUser, которая добавляет в БД пользователя с fname
Richie и age
22. В ответ на этот запрос сервер присылает JSON с id записи. Ответ выглядит так:
data : {
addUser : "a36e4h"
}
Subscription: подписки в GraphQL
Subscription — третий тип операций в GraphQL. С его помощью клиент слушает изменения в БД в режиме реального времени. Под капотом подписки используют вебсокеты. Пример кода:
subscription listenLikes {
listenLikes {
fname
likes
}
}
С помощью этого запроса можно получать список пользователей с именами и количеством лайков каждый раз, когда оно меняется.
Например, когда пользователь с fname
Richie получает лайк, ответ будет таким:
data: {
listenLikes: {
"fname": "Richie",
"likes": 245
}
}
Подобный запрос можно использовать для обновления количества лайков в режиме реального времени в соответствующем интерфейсе, например, в форме с результатами голосования на сайте.
Выше представлены три основных типа запросов в GraphQL. Несмотря на поверхностное знакомство, вы получили достаточно знаний, чтобы начать работать со схемой GraphQL.
Как работать с сервером GraphQL
Рассмотрим ответ на этот вопрос на конкретном примере.
Цель: настроить сервер GraphQL, который отвечает на три типа запросов к БД NoSQL, в которой есть список пользователей с такой структурой:
[{
"id": 1,
"fname": "Richie",
"age": 27,
"likes": 8
},
{
"id": 2,
"fname": "Betty",
"age": 20,
"likes": 205
},
{
"id" : 3,
"fname": "Joe",
"age": 28,
"likes": 10
}]
Также в базе данных есть списки постов, которые опубликовали пользователи.
[{
id: 1,
userId: 2,
body: "Hello how are you?"
},
{
id: 1,
userId: 3,
body: "What's up?"
},
{
id: 1,
userId: 1,
body: "Let's learn GraphQL"
}]
Для дальнейшей работы понадобится сервер Apollo. Это сервер GraphQL с открытым исходным кодом.
Настройте проект и установите зависимости:
> mkdir graphql-server-example
> cd graphql-server-example
> npm init -y
Вы создали пустой проект. Теперь установите GraphQL, Apollo и nodemon для отслеживания изменений в файлах.
> npm install apollo-server graphql nodemon
Чтобы nodemon работал, добавьте в package.json
запись:
"scripts": "start": "nodemon -e js, json, graphql"
База данных
Данные в этом примере поместим в переменную JSON. В реальных проектах данные обычно хранятся в БД. В тестовом проекте данные хранятся в index.js. Вот они:
const users = [
{
id: 1,
fname: 'Richie',
age: 27,
likes: 0,
},
{
id: 2,
fname: 'Betty',
age: 20,
likes: 205,
},
{
id: 3,
fname: 'Joe',
age: 28,
likes: 10,
},
];
const posts = [
{
id: 1,
userId: 2,
body: "Hello how are you?"
},
{
id: 1,
userId: 3,
body: "What's up?"
},
{
id: 1,
userId: 1,
body: "Let's learn GraphQL"
},
]
Теперь можно перейти к работе с сервером GraphQL.
Схема
Работа с сервером GraphQL всегда начинается с разработки схемы (Schema). Она состоит из двух взаимосвязанных объектов: TypeDefs и Resolvers.
Выше были описаны основные типы GraphQL. Чтобы сервер мог с ними работать, эти типы необходимо определить. Объект typeDef определяет список типов, которые доступны в проекте. Код выглядит так:
const typeDefs = gql`
type User {
id: Int
fname: String
age: Int
likes: Int
posts: [Post]
}
type Post {
id: Int
user: User
body: String
}
type Query {
users(id: Int!): User!
posts(id: Int!): Post!
}
type Mutation {
incrementLike(fname: String!) : [User!]
}
type Subscription {
listenLikes : [User]
}
`;
В примере выше определяется тип User, в котором указываются fname
, age
, likes
и другие данные. Для каждого поля определяется тип данных: String
или Int
. GraphQL поддерживает четыре типа данных: String
, Int
, Float
, Boolean
. Если в поле указан восклицательный знак, оно становится обязательным.
Также в примере выше определяются типы Query
,Mutation
и Subscription
.
Первый тип, который содержит внутри себя тип Query
, называется users
. Он принимает id и возвращает объект с данными соответствующего пользователя. Это обязательное поле. Ещё один тип Query
называется posts
. Он устроен так же, как users
.
type Query {
users(id: Int!): User!
posts(id: Int!): Post!
}
Тип Mutation
называется incrementLike
. Он принимает параметр fname
и возвращает список пользователей.
type Mutation {
incrementLike(fname: String!) : [User!]
}
Тип Subscription
называется listenLikes
. Он возвращает список пользователей.
type Subscription {
listenLikes : [User]
}
После определения типов необходимо добавить их логику. Это нужно, чтобы сервер знал, как отвечать на запросы клиента. Эта задача решается с помощью Resolvers.
Также полезно Когда Gatsby заменит WordPress: интервью с GraphQL-гуру Михаилом Новиковым.
Resolver
Resolver или распознаватель — функция, которая возвращает данные для определённого поля. Resolver’ы возвращают данные того типа, который определён в схеме. Распознаватели могут быть асинхронными. С их помощью можно получать данные из REST API, базы данных или другого источника.
Определим Resolver’ы:
const resolvers = {
Query: {
users(root, args) { return users.filter(user => user.id === args.id)[0] },
posts(root, args) { return posts.filter(post => post.id === args.id)[0] }
},
User: {
posts: (user) => {
return posts.filter(post => post.userId === user.id)
}
},
Post: {
user: (post) => {
return users.filter(user => user.id === post.userId)[0]
}
},
Mutation: {
incrementLike(parent, args) {
users.map((user) => {
if(user.fname === args.fname) user.likes++
return user
})
pubsub.publish('LIKES', {listenLikes: users});
return users
}
},
Subscription: {
listenLikes: {
subscribe: () => pubsub.asyncIterator(['LIKES'])
}
}
};
В примере выше есть шесть функций:
- запрос
users
возвращает объект пользователя, соответствующий переданному id; - запрос
posts
возвращает объект поста, соответствующий переданному id; - в поле
posts
User
распознаватель принимает данные пользователя и возвращает список его постов; - в поле
user
Posts
функция принимает данные поста и возвращает пользователя, который опубликовал пост; - мутация
incrementLike
изменяет объектusers
: увеличивает количествоlikes
для пользователя с соответствующимfname
. После этогоusers
публикуются вpubsub
с названиемLIKES
; - подписка
listenLikes
слушаетLIKES
и отвечает при обновленииpubsub
.
Два слова о pubsub
. Этот инструмент представляет собой систему передачи информации в режиме реального времени с использованием вебсокетов. pubsub
удобно использовать, так как всё, что касается вебсокетов, вынесено в отдельные абстракции.
Итак, работа с типами и распознавателями завершена. Теперь можно запустить сервер.
Запускаем сервер
Откройте http://localhost:4000/ в браузере. Благодаря Apollo вы можете полноценно протестировать сервер GraphQL.
Потратили 15 минут на настройку сервера и, вуаля, написали первый запрос. Полная версия кода опубликована здесь.
REST vs. GraphQL
Что делать, если нам нужно найти пользователя, который опубликовал пост с id x? Рассмотрим, как эта задача решается с помощью REST. Вот данные:
Users:
{
"id": 1,
"fname": "Richie",
"age": 27,
"likes": 8
},
{
"id": 2,
"fname": "Betty",
"age": 20,
"likes": 205
},
{
"id" : 3,
"fname": "Joe",
"age": 28,
"likes": 10
}
Posts:
{
id: 1,
userId: 2,
body: "Hello how are you?"
},
{
id: 1,
userId: 3,
body: "What's up?"
},
{
id: 1,
userId: 1,
body: "Let's learn GraphQL"
}
Сначала необходимо определить endpoint’ы GET:
> https://localhost:4000/users/:id
> https://localhost:4000/posts/:id
Когда необходимо получить данные о пользователе, который опубликовал пост с id 1, запрос выглядит так:
GET https://localhost:4000/posts/1
Response:
{
id: 1,
userId: 2,
body: "Hello how are you?"
}
GET https://localhost:4000/users/2
Response:
{
"id": 2,
"fname": "Betty",
"age": 20,
"likes": 205
}
Чтобы получить нужные данные, приходится дважды обращаться к серверу.
Что делать, если нужно получить только имя пользователя? Конечно, можно использовать данные поля fname
. Но мы уже получили дополнительные данные: id
, likes
, age
. Операция становится слишком дорогой.
Можно попробовать использовать endpoint, который получает только данные из поля fname
:
https://localhost:4000/userByfname/:id
Это решает проблему. Но сложность в том, что вам придётся создавать endpoint для каждого типа информации. Это неудобно.
Давайте посмотрим, как можно решить задачу с помощью GraphQL. Всё, что вам требуется — простой запрос:
{
posts(id: 1) {
body
user {
fname
}
}
}
Если нужно получить age
пользователя, который опубликовал пост с id 1, запрос будет таким:
{
posts(id: 1) {
body
user {
age
}
}
}
Поле user
в запросе определено в схеме. Поэтому вы можете получать нужную информацию без использования endpoint’ов и повторных обращений к серверу.
Изучайте Node.js на Хекслете Первые курсы в профессии «Node.js-программист» доступны бесплатно. Регистрируйтесь и начинайте учиться.
Заключение
В руководстве рассмотрена базовая информация о GraphQL: краткая теория, типы, отличия от REST. В комментариях можно поделиться впечатлениями о GraphQL и опытом применения этого инструмента.
Адаптированный перевод статьи So, What the Heck is GraphQL by Karthik Kalyanaraman. Мнение автора оригинальной публикации может не совпадать с мнением администрации «Хекслета».
- Освойте азы современных языков программирования
- Изучите работу с Git и командной строкой
- Выберите себе профессию или улучшите навыки