Главная | Все статьи | Код

Что такое GraphQL: с основ до первых запросов

Время чтения статьи ~10 минут
Что такое GraphQL: с основ до первых запросов главное изображение

Это руководство по GraphQL. Из него вы узнаете базовую теорию, а также научитесь писать простые запросы с помощью GraphQL.

Что такое GraphQL: теоретический ликбез

Прежде чем рассматривать GraphQL, давайте уделим внимание исторической базе. Что такое SQL — structured query language или язык структурированных запросов?

SQL — декларативный язык программирования, который применяется для создания, изменения и управления данными в базах данных. Этот язык поддерживает четыре базовых оператора запросов: SELECT, INSERT, UPDATE и DELETE. С помощью SQL мы можем запросить из базы данных (БД) именно то, что нам необходимо.

Бесплатные курсы по программированию в Хекслете
  • Освойте азы современных языков программирования
  • Изучите работу с Git и командной строкой
  • Выберите себе профессию или улучшите навыки
Выбрать курс

Например, когда необходимо «достать» из БД всех пользователей с именем Maria, это можно сделать с помощью запроса:


SELECT * FROM USERS WHERE FirstName = "Maria"

Решить эту задачу с помощью REST можно несколькими способами:

  1. Определить endpoint на сервере, который будет отдавать из базы данных пользователей с fname Maria.
  2. Определить общий 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 удобно использовать, так как всё, что касается вебсокетов, вынесено в отдельные абстракции.

Итак, работа с типами и распознавателями завершена. Теперь можно запустить сервер.

Запускаем сервер GraphQL

Запускаем сервер


Откройте 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 и командной строкой
  • Выберите себе профессию или улучшите навыки
Выбрать курс

Аватар пользователя Дмитрий Дементий
Дмитрий Дементий 21 сентября 2019
31
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 25 апреля
профессия
от 6 183 ₽ в месяц
Ручное тестирование веб-приложений
4 месяца
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 25 апреля
профессия
от 5 025 ₽ в месяц
новый
Сбор, анализ и интерпретация данных
9 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка веб-приложений на Laravel
10 месяцев
с нуля
Старт 25 апреля
профессия
от 5 840 ₽ в месяц
Создание веб-приложений со скоростью света
5 месяцев
c опытом
Старт 25 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
в разработке
Старт 25 апреля
профессия
Верстка с использованием последних стандартов CSS
5 месяцев
с нуля
Старт в любое время