GraphQL — это язык запросов к API-интерфейсам. Он отображает предоставляемые сервером данные, чтобы клиент смог выбрать именно то, что ему нужно.
Помимо этого, с помощью GraphQL можно получить несколько ресурсов сервера одним вызовом, а не выполнять множество вызовов REST API.
Однако трудно оценить преимущества GraphQL, не попробовав его в действии. Поэтому рассмотрим использование GraphQL на практических примерах.
Мы будем использовать GraphQL вместе с NodeJS.
Предварительные требования
Перед началом работы установите NodeJS.
Как использовать GraphQL с NodeJs
GraphQL используется с различными ЯП. Рассмотрим пример использования GraphQL с JavaScript, используя NodeJS.
Создайте папку под названием graphql-with-nodejs. Запустите команду npm init
в папке проекта, чтобы создать проект NodeJS. Команда представлена ниже:
cd graphql-with-nodejs npm init
Установка зависимостей
Установите Express с помощью следующей команды:
npm install express
Установите GraphQL с помощью следующей команды. Устанавливаем GraphQL и GraphQL для Express.
npm install express-graphql graphql
Код NodeJS
Создайте файл server.js внутри проекта и скопируйте в него следующий код:
const express = require('express');
const port = 5000;
const app = express();
app.get('/hello', (req,res) => {
res.send("hello");
}
);
app.listen(port);
console.log(`Server Running at localhost:${port}`);
Приведенный код содержит одну конечную точку (endpoint) HTTP GET с названием /hello.
Endpoint создается с помощью Express.
Теперь изменим этот код, чтобы запустить GraphQL.
Запуск GraphQL в коде
У GraphQL будет один URL endpoint с именем /graphql, который обрабатывает все запросы.
Скопируйте следующий код в server.js:
//get all the libraries needed
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {GraphQLSchema} = require('graphql');
const {queryType} = require('./query.js');
//setting up the port number and express app
const port = 5000;
const app = express();
// Define the Schema
const schema = new GraphQLSchema({ query: queryType });
//Setup the nodejs GraphQL server
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
app.listen(port);
console.log(`GraphQL Server Running at localhost:${port}`);
Теперь пройдемся по коду.
С помощью graphqlHTTP сервер GraphQL устанавливается в URL-адрес /graphql. Он обрабатывает поступающие запросы.
Процесс установки представлен в следующих строчках кода:
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
Теперь изучим параметры внутри graphqlHTTP.
graphiql
graphiql — это веб-интерфейс, с помощью которого тестируются endpoints GraphQL. Устанавливаем значение true, чтобы было легче тестировать различные endpoints GraphQL.
schema
У GraphQL есть лишь один endpoint /graphql. Он может обладать несколькими endpoints, выполняющими различные действия. Они указаны в схеме.
Схема выполняет следующие действия:
- Указывает различные endpoints
- Определяет поля ввода и вывода для endpoint
- Определяет действие, которое должно быть выполнено при достижении endpoint и так далее.
Схема определяется в коде следующим образом:
const schema = new GraphQLSchema({ query: queryType });
Схема может содержать как запрос, так и типы мутации. Мы будем рассматривать только тип query.
query
По схеме видно, что query установлен в queryType.
Импортируем queryType из файла query.js с помощью следующей команды:
const {queryType} = require('./query.js');
query.js — это пользовательский файл.
query — это место, куда мы определяем endpoints типа read-only в схеме.
Создайте файл с названием query.js в проекте и скопируйте в него следующий код.
const { GraphQLObjectType,
GraphQLString
} = require('graphql');
//Define the Query
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: function () {
return "Hello World";
}
}
}
});
exports.queryType = queryType;
query Explained
queryType создан как GraphQLObjectType с названием Query.
fields — это место, куда мы определяем endpoints.
Здесь мы добавляем один endpoint под названием hello.
hello относится к типу GraphQLString, т.е возвращает тип String. В схеме GraphQL используется тип GraphQLString вместо String. Прямое использованиеString не работает.
Функция resolve указывает действие, которое должно быть выполнено при достижении endpoint. В данном случае возвращается строка “Hello World”.
Теперь экспортируем querytype с помощью exports.queryType = queryType
, чтобы убедиться, что он может быть импортирован в server.js.
Запуск приложения
Запустите приложение, используя следующую команду:
node server.js
Приложение работает на localhost:5000/graphql.
Можно протестировать приложение, перейдя на localhost:5000/graphql.
Этот URL-адрес управляет веб-интерфейсом Graphiql, как указано на скриншоте ниже.
Входные данные указаны слева, а выходные — справа.
Вводим следующее:
{
hello
}
В результате получаем:
{
"data": {
"hello": "Hello World"
}
}
Поздравляем?
Вы создали свой первый endpoint GraphQL.
Добавление нескольких endpoints
Создадим еще две конечных точки (endpoints):
- movie: этот endpoint возвращает фильм, получивший ID фильма
- director: этот endpoint возвращает режиссера, получившего ID режиссера. Он также вернет все фильмы, снятые этим режиссером.
Добавление данных
Обычно приложение прочитывает данные из Базы данных. Но в этом примере, мы будем хардкодить данные в самом коде.
Создайте файл data.js и скопируйте в него следующий код.
//Hardcode some data for movies and directors
let movies = [{
id: 1,
name: "Movie 1",
year: 2018,
directorId: 1
},
{
id: 2,
name: "Movie 2",
year: 2017,
directorId: 1
},
{
id: 3,
name: "Movie 3",
year: 2016,
directorId: 3
}
];
let directors = [{
id: 1,
name: "Director 1",
age: 20
},
{
id: 2,
name: "Director 2",
age: 30
},
{
id: 3,
name: "Director 3",
age: 40
}
];
exports.movies = movies;
exports.directors = directors;
В этом файле содержатся данные о фильмах и режиссерах. Используем данные из этих файлов для наших endpoints.
Добавление endpoint movie в запрос
Новые endpoints будут добавлены к queryType в файл query.js.
Код для endpoint movie представлен ниже:
movie: {
type: movieType,
args: {
id: { type: GraphQLInt }
},
resolve: function (source, args) {
return _.find(movies, { id: args.id });
}
}
Тип возвращаемого значения этого endpoint — movieType, который скоро будет определен.
С помощью параметра args указываются входные данные для endpoint movie. Входные данные для этого endpoint — id типа GraphQLInt.
Функция resolve возвращает фильм, соответствующий id, из списка фильмов. Функция find из библиотеки lodash используется для поиска элемента в списке.
Полный код для query.js представлен ниже:
const { GraphQLObjectType,
GraphQLString,
GraphQLInt
} = require('graphql');
const _ = require('lodash');
const {movieType} = require('./types.js');
let {movies} = require('./data.js');
//Define the Query
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: function () {
return "Hello World";
}
},
movie: {
type: movieType,
args: {
id: { type: GraphQLInt }
},
resolve: function (source, args) {
return _.find(movies, { id: args.id });
}
}
}
});
exports.queryType = queryType;
Из приведенного кода видно, что movieType действительно определен в types.js.
Добавление пользовательского типа movieType
Создайте файл с названием types.js.
Добавьте следующий код в types.js:
const {
GraphQLObjectType,
GraphQLID,
GraphQLString,
GraphQLInt
} = require('graphql');
// Define Movie Type
movieType = new GraphQLObjectType({
name: 'Movie',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
year: { type: GraphQLInt },
directorId: { type: GraphQLID }
}
});
exports.movieType = movieType;
movieType создан в качестве GraphQLObjectType.
Он обладает четырьмя полями: id, name, year и directorId. Типы для каждого поля устанавливаются в процессе их добавления.
Эти поля поступают непосредственно из данных. В данном случае, из списка movies.
Добавление запроса и типа для endpoint director
В query.js endpoint director добавляется следующим образом:
director: {
type: directorType,
args: {
id: { type: GraphQLInt }
},
resolve: function (source, args) {
return _.find(directors, { id: args.id });
}
}
directorType добавляется следующим образом в types.js:
//Define Director Type
directorType = new GraphQLObjectType({
name: 'Director',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
age: { type: GraphQLInt },
movies: {
type: new GraphQLList(movieType),
resolve(source, args) {
return _.filter(movies, { directorId: source.id });
}
}
}
});
Минуточку. directorType немного отличается от movieType. В чем причина?
Почему функция resolve находится внутри directorType? Ранее функции resolve присутствовали только в query…
Особенности directorType
При вызове endpoint director необходимо возвратить подробную информацию о режиссере, а также всего его фильмы.
Первые три поля id, name, age в directorType исходят непосредственно из данных (список directors).
Четвертое поле movies должно содержать список фильмов этого режиссера.
По этой причине, следует упомянуть, что тип поля movies относится к GraphQLList от movieType (Список фильмов).
Однако каким образом найти все фильмы, снятие этим режиссером?
Для этого в поле movies есть функция resolve. Входные данные для функции resolve — source и args.
source будет содержать подробную информацию о родительском объекте.
Допустим, поля для режиссера id =1, name = “Random” и age = 20. Затем source.id =1, source.name = “Random” и source.age = 20
Таким образом, в этом примере функция resolve найдет все фильмы, в которых directorId соответствует Id запрашиваемого режиссера.
Тестирование приложения
Протестируем приложение для различных сценариев.
Запустите приложение с помощью node server.js
.
Зайдите на localhost:5000/graphql и введите следующие данные.
movie
Входные данные:
{
movie(id: 1) {
name
}
}
Результат:
{
"data": {
"movie": {
"name": "Movie 1"
}
}
}
Приведенный пример показывает, что клиент может запросить именно то, что ему нужно, а GraphQL предоставит необходимые параметры. Сервер выдает только запрошенное поле name.
В movie(id: 1)
id находится во входном параметре. Мы запросили у сервера фильм с id=1.
Входные данные:
{
movie(id: 3) {
name
id
year
}
}
Выход:
{
"data": {
"movie": {
"name": "Movie 3",
"id": "3",
"year": 2016
}
}
}
В приведенном выше примерe сервер выдает запрашиваемые поля name, id и year.
director
Входные данные:
{
director(id: 1) {
name
id,
age
}
}
Результат:
{
"data": {
"director": {
"name": "Director 1",
"id": "1",
"age": 20
}
}
}
Входные данные:
{
director(id: 1) {
name
id,
age,
movies{
name,
year
}
}
}
Получаем:
{
"data": {
"director": {
"name": "Director 1",
"id": "1",
"age": 20,
"movies": [
{
"name": "Movie 1",
"year": 2018
},
{
"name": "Movie 2",
"year": 2017
}
]
}
}
}
Приведенный пример показывает возможности GraphQL. Нам нужен режиссер с id=1 и все его фильмы. Поля director и movie являются произвольными, поэтому клиент может запросить именно то, что ему нужно.
Аналогичным образом можно запрашивать другие поля и типы. К примеру, можно сделать запрос: Поиск режиссера с id=1 и всех его фильмов. Поиск актеров для каждого фильма, а для каждого актера 5 его лучших фильмов и так далее. Для этого запроса необходимо указать отношения между типами и тогда можно будет запросить любое необходимое отношение.
Поздравляем ?
Теперь вы знакомы с базовыми концепциями GraphQL.
Чтобы узнать больше, можете посмотреть официальную документацию.
Перевод статьи Aditya Sridhar: An introduction to GraphQL: how it works and how to use it