В этой статье вы узнаете, как работает 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
, вы должны увидеть эти выходные данные в своем терминале:

Теперь вы можете перейти на 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, есть три основных понятия:
- запросы (queries) — получение данных с сервера.
- мутации (mutations) — изменение данных на сервере и получение обновленных данных обратно.
- подписки (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) сомневаюсь что есть тип даних ID
2) при Query user(id: 1) получаем ошибку
return users.find(user => user.id === id);
из-за того что тут идет строгое сравнение ===
id — ето число
а user.id ето строка и код уже не пашет, в схеме надо менять тип ID на Int, или место === поставить == что би тип перемених не проверяло
3) subscription не пашет.. жаль, буду читать по силке что там
Great content! Super high-quality! Keep it up! 🙂