GraphQL

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

Предыдущая статьяС Kotlin приведение стало еще удобнее
Следующая статья10 тенденций в разработке программного обеспечения в 2019 году