GraphQL: руководство для начинающих

В этой статье вы узнаете, как работает GraphQL. Мы покажем вам, как создать продуманный, эффективный и мощный API.

Одним из наиболее часто обсуждаемых сегодня терминов является API. Application Programming Interface (API) — это, как говорится в названии, интерфейс, с которым люди — разработчики, пользователи, потребители — могут взаимодействовать с данными.

Что такое GraphQL?

GraphQL — это язык запросов с открытым исходным кодом, разработанный Facebook. Он позволяет нам более эффективно проектировать, создавать и использовать наш API.

GraphQL имеет множество функций:

  • Вы запрашиваете данные, которые хотите, и получаете именно те данные, которые хотите. Избавление от over-fetching
  • Единственная конечная точка (single endpoint)
  • GraphQL строго типизирован, и с этим вы можете проверить запрос в системе типов GraphQL перед выполнением. Это помогает нам создавать более мощные API

Заходите на сайт GraphQL, если хотите узнать больше.

Начало работы

Основная цель этой статьи — не научиться настраивать сервер GraphQL, а узнать, как работает GraphQL на практике, поэтому мы будем использовать сервер GraphQL с нулевой конфигурацией (zero-configuration) под названием Graphpack.

Чтобы начать наш проект, мы создадим новую папку, вы можете назвать ее как угодно, например graphql-server. Откройте терминал и введите:

mkdir graphql-server

В созданной папке введите следующую команду:

npm init -y

Eсли вы используете yarn:

yarn init 

npm создаст package.json, все установленные зависимости, и ваши команды будут там.

Теперь мы установим единственную зависимость, которую будем использовать.

В терминале, в корневой папке, установите его следующим образом:

npm install --save-dev graphpack

Если используете yarn:

yarn add --dev graphpack

После установки Graphpack перейдите к нашим скриптам в package.jsonфайле и поместите туда следующий код:

"scripts": {
    "dev": "graphpack",
    "build": "graphpack build"
}

Мы создадим папку src, и она будет единственной папкой на нашем сервере. После этого внутри нашей папки мы создадим три файла.

Создаем первый файл с именемschema.graphql, и помещаем туда следующий код:

type Query {
  hello: String
}

В этомschema.graphqlфайле будет вся наша GraphQL структура (schema).

Создаем второй файл с именемresolvers.js, и помещаем туда следующий код:

import { users } from "./db";

const resolvers = {
  Query: {
    hello: () => "Hello World!"
  }
};

export default resolvers;

Наконец создаем третий файл с именем db.js, и помещаем туда следующий код:


export let users = [
  { id: 1, name: "John Doe", email: "john@gmail.com", age: 22 },
  { id: 2, name: "Jane Doe", email: "jane@gmail.com", age: 23 }
];

В этом уроке мы не используем реальную базу данных, так что db.js файл будет имитировать базу данных, только для учебных целей.

Наша папка srcдолжна выглядеть так:

src
  |--db.js
  |--resolvers.js
  |--schema.graphql

Если вы запустите команду npm run dev или, если вы используете yarn yarn dev, вы должны увидеть эти выходные данные в своем терминале:

img

Теперь вы можете перейти на localhost: 4000 . Это означает, что мы готовы приступить к написанию наших первых запросов (queries), мутаций (mutations) и подписок (subscriptions) в GraphQL.

Структура (Schema)

GraphQL имеет свой тип языка, используемый для записи структур. Это понятный синтаксис структуры, называемый Schema Definition Language (SDL), с ним вам будет легко понять, какие типы будет иметь API. Вы можете использовать его с любым языком или фреймворком.

Типы (Types)

Типы являются одной из наиболее важных особенностей GraphQL. Типы — это настраиваемые объекты, представляющие внешний вид API. Например, при создании приложения для социальных сетей API должен иметь такие типы, как Posts, Users, Likes, Groups.

Типы имеют поля (fields), и эти поля возвращают определенный тип данных. Например, мы создадим тип пользователя (User type), у нас должны быть поля name, email и age. Тип полей может быть любым — Int, Float, String, Boolean, ID, List of Object Types, или Custom Objects Types.

Итак, теперь, чтобы написать наш первый тип, переходим к нашему schema.graphql файлу и заменяем тип Query, который уже существует, следующим:

type User {
  id: ID!
  name: String!
  email: String!
  age: Int
}

Каждый User будет иметь id, с типом ID. User также будет иметь name и email, с типом String, и age с типом Int. Довольно просто, правда?

Но, что за ! в конце каждой строчки? Восклицательный знак означает, что поля не допускают обнуления (non-nullable), то есть каждое поле должно возвращать некоторые данные в каждом запросе. Единственное обнуляемое поле (nullable) в нашем User будет age.

В GraphQL, есть три основных понятия:

  1. запросы (queries) — получение данных с сервера.
  2. мутации (mutations) — изменение данных на сервере и получение обновленных данных обратно.
  3. подписки (subscriptions) — поддержание соединения с сервером в режиме реального времени.

Queries

Проще говоря, запросы (queries) в GraphQL — это то, как вы собираетесь запрашивать данные. Вы получите именно те данные, которые вам нужны. Не больше, не меньше.

Создадим наш первый тип Query в GraphQL. Все наши запросы окажутся внутри этого типа. Итак, для начала перейдем к нашему schema.graphql и напишем новый тип с именем Query:

type Query {
  users: [User!]!
}

Все очень просто: запрос users вернет нам массив из одного или нескольких users. Он не вернет null, так как мы добавляем !, а это означает , что запрос необнуляемый (non-nullable). Он всегда должен что-то возвращать.

Но мы также можем вернуть конкретного пользователя. Для этого мы создадим новый запрос user. Внутри нашего типа Query, помещаем следующий код:

user(id: ID!): User!

Теперь тип Query должен выглядеть следующим образом:

type Query {
  users: [User!]!
  user(id: ID!): User!

Как видите, с запросами в GraphQL мы также можем передавать аргументы. В этом случае для запроса конкретного user мы передадим его ID.

Откуда GraphQL знает, где получить данные? Из файла resolvers.js! Этот файл будет отвечать за обработку запросов GraphQL.

Сначала перейдем к нашему resolvers.js и импортируем db.js. Наш resolvers.js будет выглядеть следующим образом:

import { users } from "./db";

const resolvers = {
  Query: {
    hello: () => "Hello World!"
  }
};

export default resolvers;

Теперь мы создадим наш первый запрос (Query). Перейдем к нашему resolvers.js и заменим функцию hello. Теперь наш тип Query должен выглядеть так:

import { users } from "./db";

const resolvers = {
  Query: {
    user: (parent, { id }, context, info) => {
      return users.find(user => user.id === id);
    },
    users: (parent, args, context, info) => {
      return users;
    }
  }
};

export default resolvers;

Объясним, как это работает:

Каждый резолвер имеет четыре аргумента. В функцию user мы передадим id в качестве аргумента, а затем вернем конкретный user, соответствующий переданному id. Довольно просто.

В функции users мы просто вернем массив users, который уже существует. Она всегда будет возвращать нам всех наших пользователей.

Теперь мы проверим, работают ли наши запросы. Перейдите на localhost: 4000 и введите следующий код:

query {
  users {
    id
    name
    email
    age
  }
}

Он должен вернуть вам всех наших пользователей.

Или, если вы хотите вернуть определенного пользователя:

query {
  user(id: 1) {
    id
    name
    email
    age
  }
}

Mutations

Мутации в GraphQL — это способ изменения данных на сервере и получения обновленных данных обратно.

Создадим наш первый тип Mutation в GraphQL, и все наши мутации окажутся внутри этого типа. Итак, для начала зайдем в schema.graphql и напишем новый тип с именем mutation:

type Mutation {
  createUser(id: ID!, name: String!, email: String!, age: Int): User!
  updateUser(id: ID!, name: String, email: String, age: Int): User!
  deleteUser(id: ID!): User!
}

Как видите, у нас будет три мутации:

createUser: передаем ID, name, email и age.

updateUser: передаем ID и новое name, email или age.

deleteUser: передаем ID.

Перейдем к resolvers.js и под объектом Query создадим новый объект mutation:

Mutation: {
    createUser: (parent, { id, name, email, age }, context, info) => {
      const newUser = { id, name, email, age };

      users.push(newUser);

      return newUser;
    },
    updateUser: (parent, { id, name, email, age }, context, info) => {
      let newUser = users.find(user => user.id === id);

      newUser.name = name;
      newUser.email = email;
      newUser.age = age;

      return newUser;
    },
    deleteUser: (parent, { id }, context, info) => {
      const userIndex = users.findIndex(user => user.id === id);

      if (userIndex === -1) throw new Error("User not found.");

      const deletedUsers = users.splice(userIndex, 1);

      return deletedUsers[0];
    }
  }

Теперь наш resolvers.js выглядит следующим образом:

import { users } from "./db";

const resolvers = {
  Query: {
    user: (parent, { id }, context, info) => {
      return users.find(user => user.id === id);
    },
    users: (parent, args, context, info) => {
      return users;
    }
  },
  Mutation: {
    createUser: (parent, { id, name, email, age }, context, info) => {
      const newUser = { id, name, email, age };

      users.push(newUser);

      return newUser;
    },
    updateUser: (parent, { id, name, email, age }, context, info) => {
      let newUser = users.find(user => user.id === id);

      newUser.name = name;
      newUser.email = email;
      newUser.age = age;

      return newUser;
    },
    deleteUser: (parent, { id }, context, info) => {
      const userIndex = users.findIndex(user => user.id === id);

      if (userIndex === -1) throw new Error("User not found.");

      const deletedUsers = users.splice(userIndex, 1);

      return deletedUsers[0];
    }
  }
};

export default resolvers;

Теперь мы проверим, хорошо ли работают наши мутации. Перейдем на localhost: 4000 и вводим следующий код:

mutation {
  createUser(id: 3, name: "Robert", email: "robert@gmail.com", age: 21) {
    id
    name
    email
    age
  }
}

Он должен вернуть вам нового пользователя. Попробуйте самостоятельно удалить созданного пользователя, чтобы убедиться, что все работает нормально.

Subscriptions

Как мы уже говорили, Subscriptions — это способ поддерживать соединение с сервером в режиме реального времени. Это означает, что всякий раз, когда событие происходит на сервере и когда это событие вызывается, сервер будет отправлять соответствующие данные клиенту.

Работая с подписками (subscriptions), вы можете посылать все обновления с сервера своим пользователям.

Базовая подписка (subscription) выглядит следующим образом:

subscription {
  users {
    id
    name
    email
    age
  }
}

Вы скажете, что это очень похоже на запрос (query), да, это так, но работает по-другому.

Когда что-то обновляется на сервере, сервер запускает запрос GraphQL, указанный в подписке (subscription), и отправляет клиенту обновленный результат.

Мы не будем работать с подписками в этой статье, но если вы хотите узнать больше о них, можете почитать здесь.

Заключение

Как вы видите, GraphQL — это действительно мощная технология, которая дает нам реальную возможность создавать великолепные и хорошо продуманные API. В конечном итоге он может заменить вам REST.

Оригинал статьи

1 комментарий

  • статья хорошая, но есть ошибки, к примеру
    1) сомневаюсь что есть тип даних ID
    2) при Query user(id: 1) получаем ошибку
    return users.find(user => user.id === id);
    из-за того что тут идет строгое сравнение ===
    id — ето число
    а user.id ето строка и код уже не пашет, в схеме надо менять тип ID на Int, или место === поставить == что би тип перемених не проверяло
    3) subscription не пашет.. жаль, буду читать по силке что там