Как разработать API с использованием GraphQL
Как устроены запросы в GraphQL
В основе любой реализации GraphQL лежит описание типов объектов, которые она будет возвращать, и функция того, что с этими объектами делать. Если вы знаете, какие поля можно выбрать, какие типы объектов они могут возвращать, какие поля доступны для этих подобъектов, то всё это для API-интерфейса можно описать в так называемой схеме. И поскольку форма запроса очень близка к виду результата, вы можете предсказать, что вернёт запрос, не зная в точности, как и что хранится на сервере.
При работе с сервером в ней описываются все сущности GraphQL, которые мы рассмотрели выше. Чтобы не путаться, можно называть все эти сущности полями в схеме. Схема в GraphQL определяет, как будут сформированы и связаны между собой данные и какая функция какому полю будет назначена. Определение схемы влияет на то, как данные будут храниться в базе данных. Клиент может запрашивать только те поля, что определены в схеме.
GraphQL использует систему типов для проверки запросов, отправляемых клиентом. Система типов описывает то, что может быть возвращено с сервера. По мере поступления GraphQL проверяет запросы на соответствие схеме, а затем выполняет проверенные.
Базовыми компонентами схемы GraphQL считаются объектные типы. Вот как они выглядят:
type Character {
name: String!
appearsIn: [Episode!]!
}
В этом выражении можно выделить несколько составляющих:
- Character — объектный тип GraphQL. То есть это тип с некоторыми полями. Большинство типов в схеме — объектные. Каждое поле объектного типа GraphQL может иметь ноль аргументов или более. Например, поле length ниже:
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
- name и appearsIn — поля Character-типа. Это единственные поля, которые могут отображаться в любой части запроса GraphQL, работающего с Character-типом. String — это один из встроенных скалярных типов, которые преобразуются в один скалярный объект и не могут иметь подвыборок в запросе. GraphQL поддерживает пять скалярных типов:
-
Int — 32-разрядное целое число со знаком.
-
Float — значение с плавающей запятой двойной точности со знаком.
-
String — последовательность символов UTF-8.
-
Boolean — true или false.
-
ID — используется как уникальный идентификатор.
-
String! означает, что поле не имеет значения null. То есть служба GraphQL обещает всегда предоставлять вам значение при запросе этого поля. На языке типов мы будем представлять их восклицательным знаком.
-
[Episode!]! представляет собой массив объектов. Поскольку оно также не имеет значения null, при запросе appearsIn поля в ответ можно ожидать массив (с нулём элементов или более). При этом каждый элемент массива будет объектом Episode.
Синтаксис для написания схем определяется собственным языком Schema Definition Language (SDL), это часть официальной документации GraphQL. SDL легко использовать, потому что он прост, интуитивно понятен и обладает множеством функций. С его помощью описываются такие составляющие схемы, как типы данных, запросы, мутации, подписки и распознаватели. Запрос в виде схемы выглядит примерно так:
const schema = {
typeDefs: gql`
type Book {
id: ID!
title: String!
author: String!
description: String
}
type Query {
books: [Book!]!
book(id: ID!): Book
}
type Mutation {
createBook(title: String!, author: String!, description: String): Book!
updateBook(id: ID!, title: String, author: String, description: String): Book!
deleteBook(id: ID!): Book!
}
type Subscription {
bookCreated: Book!
bookUpdated(id: ID!): Book!
bookDeleted(id: ID!): Book!
}
`;
resolvers: {
Query: {
books: () => {
return Book.find();
},
book: (_, { id }) => {
return Book.findById(id);
},
},
Mutation: {
createBook: (_, { title, author, description }) => {
const book = new Book({ title, author, description });
book.save();
pubsub.publish('bookCreated', { bookCreated: book });
return book;
},
updateBook: (_, { id, title, author, description }) => {
const book = Book.findByIdAndUpdate(id, { title, author, description }, { new: true });
pubsub.publish('bookUpdated', { bookUpdated: book });
return book;
},
deleteBook: (_, { id }) => {
const book = Book.findByIdAndDelete(id);
pubsub.publish('bookDeleted', { bookDeleted: book });
return book;
},
},
Subscription: {
bookCreated: {
subscribe: () => pubsub.asyncIterator(['bookCreated']),
},
bookUpdated: {
subscribe: (_, { id }) => {
return pubsub.asyncIterator(['bookUpdated', id]);
},
},
bookDeleted: {
subscribe: (_, { id }) => {
return pubsub.asyncIterator(['bookDeleted', id]);
},
},
},
}
Здесь мы определяем часть схемы typeDefs, которая содержит типы данных Book, Query (запросы), Mutation (мутации) и Subscription (подписки).
Последние три — это специальные типы GraphQL, которые представляют основные функции API в GraphQL. Запросы — это аналоги GET в REST, в то время как мутации представляют запросы POST, PUT, DELETE. Мутации нужны для создания, обновления, удаления данных на сервере. Подписки, в отличие от запросов и изменений, которые проводятся по типичному циклу «запрос-ответ», возвращают поток данных клиенту в режиме реального времени. Они позволяют создавать и поддерживать соединение с сервером онлайн, что даёт клиенту возможность получать уведомления о событиях на сервере и выполнять соответствующие действия. Например, вот как можно использовать подписки для получения уведомлений о новых сообщениях в чате:
subscription {
newMessage {
id
text
author
}
}
Этот запрос подписывается на новые сообщения в чате и возвращает их идентификаторы, тексты и авторов.
Типы операций прописываются в объекте typeDef вместе другими типами данных, доступными в базе данных проекта. Можно также создать свой тип. Объект Resolver — это функция-распознаватель. Она связана с typeDefs и возвращает то, что в нём было прописано. Resolver отвечает за выборку данных для отдельного поля или всей схемы.
В чём преимущества GraphQL
GraphQL не устанавливает требований к сети, авторизации или пагинации. Разработчики могут обновлять данные в режиме реального времени с помощью возможностей GraphQL для чтения, изменения данных и мониторинга.
GraphQL не зависит от языка программирования. По сути, он служит дополнением к серверной службе, которое позволяет разработчикам взаимодействовать с базой данных и запрашивать оттуда информацию. У GraphQL есть лишь одна конечная точка — /graphql. Но сам он может связываться с несколькими конечными точками. Они указываются в схеме. Схема выполняет следующие действия:
-
Указывает различные конечные точки.
-
Определяет поля ввода и вывода для конечных точек.
-
Определяет действие, которое должно быть выполнено при достижении конечной точки.
По сути, GraphQL выступает в роли посредника или помощника. Вы в одном запросе к одной конечной точке /graphql указываете, какие данные хотите получить и откуда. А GraphQL уже сам на стороне сервера собирает данные из нескольких источников и выдаёт обратно в одном ответе. Это похоже на то, как сборщик формирует заказ, когда вы пользуетесь приложением доставки еды. Вы указываете, что вам нужно, сборщик находит эти продукты на складе и отправляет их вам.
В отличие от большинства языков запросов, таких как SQL, GraphQL не используется для запроса определённого типа хранилища данных (например, базы данных MySQL). Вместо этого GraphQL позволяет запрашивать данные из любого количества различных источников. При создании веб-страницы может потребоваться отображать разные типы данных в разных местах пользовательского интерфейса. Например, получить цену, размер, вес, описание товара и так далее — всё для одной страницы. При использовании GraphQL можно запросить различные данные, необходимые для создания пользовательского интерфейса, в одном вызове API, вместо того чтобы делать запрос несколько раз для разных источников данных. В сравнении с REST работа GraphQL устроена так:
Например, вам может понадобиться загрузить в приложение социальной сети имя пользователя, аватарку, посты, подписчиков, подписки и так далее. И для каждого есть отдельный источник данных со своим адресом URL. В REST пришлось бы отправлять однотипные запросы по этим нескольким адресам. GraphQL позволяет отправлять все запросы по одному адресу, указывая только, какие именно данные мы бы хотели получить:
query {
address(id: 123) {
user {
name
image
}
posts {
title
content
}
}
}
Здесь мы запрашиваем у адреса с идентификатором 123 имя пользователя и изображение, используя связь user, а также посты, используя связь posts. Для каждого поста мы запрашиваем заголовок и содержание.
Разработчики могут создавать API с помощью любых предпочитаемых методов, а спецификация GraphQL гарантирует, что всё это будет функционировать понятным для клиентов образом. GraphQL позволяет разработчикам получить полное представление о данных, собираемых API. В том числе дает возможность извлекать только данные, непосредственно относящиеся к запросам, а также создавать архитектуру, которая упрощает масштабирование и адаптацию API с течением времени. Когда нужно изменить, добавить или удалить отдельные функции приложения, можно просто настроить и выполнить поля, соответствующие запросу необходимой функции.
Как разработать API
Первоначальная реализация GraphQL была доступна для JavaScript. Сегодня большинство библиотек для него написаны в среде Node.js. Также Apollo и Prisma предоставляют инструменты и библиотеки с открытым исходным кодом. Для создания простейшего API на GraphQL достаточно этих трёх:
-
Graphql-js — эталонная реализация GraphQL для JavaScript.
-
Apollo-server — сервер GraphQL для Express, Connect, Hapi, Koa и других приложений.
-
Apollo-graphql-tools — создание, макет и компоновка схемы GraphQL с использованием SDL.
Для создания API с помощью GraphQL на JavaScript можно использовать любой фреймворк. Например, Express.js. Сначала установим необходимые пакеты в нашу среду разработки с помощью npm:
npm init
npm install express graphql express-graphql
Далее создаём файл server.js и добавляем следующий код:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
Определяем схему GraphQL:
const schema = buildSchema(`
type Query {
message: String
}
`);
Схема — это структура данных, к которым клиенты будут делать запросы.
Затем определяем Resolver для запроса:
const root = {
message: () => 'Привет, GraphQL!'
};
Функция Resolver (распознаватель) возвращает данные для нужного поля. Распознаватели определяют метод извлечения типов, описанных в схеме.
Создаём экземпляр приложения Express:
const app = express();
Добавляем маршрут для GraphQL API:
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true // Включаем интерфейс GraphiQL (браузерную IDE) для отладки, создания и выполнения запросов к конечным точкам
}));
Запускаем сервер на порту 3000:
app.listen(3000, () => {
console.log('Сервер запущен на порту 3000');
});
Осталось запустить в терминале сервер:
node server.js
Теперь в браузере по адресу http://localhost:3000/graphql можно обнаружить браузерную IDE GraphiQL:
Она позволяет тестировать API и предлагает автозаполнение на основе типов и полей, доступных в вашей схеме. Выполните запрос:
query {
message
}
В ответ вы получите:
{
"data": {
"message": "Привет, GraphQL!"
}
}
Готово! Теперь у вас есть простой API с использованием GraphQL. А для удобства вызова API можно установить Apollo Explorer — бесплатную облачную среду разработки GraphQL IDE, которая включает множество функций повышения производительности. Она имеет удобный минималистичный интерфейс и упрощает разработку, подсказывая возможные имена полей для запросов.
После создания сервера на Node.js для GraphQL необходимо определить структуру данных, которые будут доступны через API. Для этого можно использовать язык запросов GraphQL и определить типы данных, поля и связи между ними.
Затем необходимо определить схему запросов и мутаций GraphQL, которые будут доступны через API. Схема описывает типы данных и операции, которые могут быть выполнены над ними.
После этого потребуется реализовать функции-распознаватели для запросов и мутаций — они будут обрабатывать запросы и возвращать соответствующие данные. Эти функции могут использовать любые источники данных, такие как базы данных или другие API.
Наконец, необходимо настроить маршрутизацию запросов к соответствующим функциям-распознавателям и запустить сервер GraphQL. После этого API будет готов к использованию.