GraphQL — новинка в блоке (хотя и не совсем новая) от facebook. Так что давайте разберемся в стиле QA!

Что такое GraphQL?

GraphQL — это общий язык, разработанный facebook для запросов данных. Проект начался в 2012 году и был общедоступен с 2015 года, но первая стабильная версия вышла в октябре 2016 года. И это не касается конкретного языка. На данный момент это уже доступно для большинства популярных языков.

К чему вся эта суета? У нас уже есть REST, зачем нам еще один?

Нет ничего лучше. У всего есть свои сильные и слабые стороны. Но GraphQL сияет по следующим ключевым факторам.

  1. Меньшее количество конечных точек API. В службе REST мы хотели бы иметь несколько конечных точек для запроса разных наборов данных. Но с GraphQL у нас есть только 0 или очень мало конечных точек. С помощью одного запроса мы можем получить объект и связанные с ним объекты.
  2. Позволяет инициатору запроса определить ответ в формате JSON, а не давать предварительно закодированный ответ.
  3. Управление версиями упрощается с помощью полей, поскольку запрашивающая сторона может определить ответ. В службах REST обычно мы управляем идентификаторами версий, такими как v1, v2 и т. д. Но с GraphQL мы можем легко добавлять/удалять поля из схемы GraphQL, чтобы отражать изменения.
  4. Решает избыточную/недостаточную загрузку, поскольку ответ, который мы получаем, соответствует тому, что нам нужно, и не более того.
  5. Возможность вызова нескольких конечных точек в одном запросе.

Все еще в замешательстве? Давайте создадим себе небольшой API и посмотрим все в действии!

Пример кода для этой статьи доступен в моем репозитории Github graphql-vehicleshop.

Это будет небольшой API для создания, удаления и запроса записей о транспортных средствах. Я собираюсь использовать язык NodeJs с MongoDB в качестве базы данных для приложения. Для этого у вас должна быть уже установлена ​​MongoDB, если не спешить скачать установщик от сообщества.

Настройка проекта.

  1. Создайте новый каталог и войдите в этот каталог из терминала. Запустите следующие коды в терминале, чтобы запустить это.
  2. npm init
  3. Ответьте на небольшой набор вопросов, укажите точку входа, так как app.js и package.json будут созданы для вас. Затем создайте файл app.js внутри каталога.
  4. Установить экспресс. npm intall --save express
  5. Установите пакеты GraphQL. npm install --save graphql express-graphql
  6. Установите Mongoose в качестве ORM для MongoDB. npm install --save mongoose
  7. Это все для настройки проекта. Поскольку это всего лишь пример кода, я не буду беспокоиться об использовании переменных env для конфигураций, я буду жестко кодировать их, но помните, что для производственного кода всегда используйте конфигурации в переменных ENV. (См. методологию приложения 12 факторов)

Даже если мы используем экспресс, вы можете использовать для этого даже библиотеку restify, поскольку express-graphql поддерживает обе библиотеки.

Настройка службы.

Давайте настроим файл app.js. Сначала мы собираемся сделать несколько вещей. Сначала нам нужно открыть экспресс-сервер, а затем подключиться к mongodb.

  1. Настройка экспресс-сервера.
const express = require('express');
const app = express();
const mongoose = require('mongoose');

app.get('/', (req, res)=>{
    res.end("graphql-vehicleshop is running on 3000 port...");
})

app.listen('3000', ()=>{
    console.log("Server is starting on 3000 port...")
})

Теперь, если вы запустите код node app.js, вы увидите в терминале сообщение о том, что сервер запущен. Если вы наберете localhost:3000 в браузере, вы получите сообщение о том же.

2. Подключитесь к монгодб.

//connect to mongodb...
mongoose.connect(`mongodb://127.0.0.1/vehicledb`);
mongoose.connection.on('error', err => console.error('FAILED to connect to mongodb instance.', err));
mongoose.connection.once('open', () => console.log('Connected to mongodb instance.'));`

Вы можете поместить это прямо под импортом мангуста. Если ваша MongoDB имеет аутентификацию, вы можете изменить строку подключения.

Хорошо, теперь базовый сервис запущен и работает. Далее давайте определим схему mongodb для этой службы и схему GraphQL.

Определить схему MongoDB.

Создайте подкаталог как «модель» внутри проекта и создайте файл javascript. Я создал как «vehicle.js». Внутри него импортируйте константы мангуста и схемы и определите схему. Наконец, экспортируйте его.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const vehicle = new Schema({
    carname: String,
    year: Number,
    transmission: String,
    fuelType: String,
    engineCapacity: Number
});

module.exports = mongoose.model('vehicle', vehicle);`

Когда новые данные будут вставлены, новая коллекция внутри mongodb будет создана как «транспортное средство» с указанными атрибутами. Итак, в конце концов ваш Vehicle.js должен выглядеть так, как показано выше.

Определить схему GraphQL.

Создайте еще один подкаталог как «graphSchema» и создайте внутри него еще один файл javascript (vehicleSchemaGQL.js), чтобы определить схему GraphQL.

Здесь мы должны импортировать библиотеку graphql и модель Mongoose, которую мы создали из «model/vehicle.js». Затем создайте базовую схему, определите интерфейсы запроса и мутатора и экспортируйте их. Давай сделаем это!

  1. Импорт материала
const graphql = require('graphql');
const vehicle = require('./../model/vehicle');

2. Затем определите базовую схему GraphQL.

const VehicleSchema = new graphql.GraphQLObjectType({
    name: 'vehicle',
    description: 'a vehicle to be sold',
    fields: {
        _id: {type: graphql.GraphQLString},
        carname: {type: graphql.GraphQLString},
        year: {type: graphql.GraphQLInt},
        transmission: {type: graphql.GraphQLString},
        fuelType: {type: graphql.GraphQLString},
        engineCapacity: {type: graphql.GraphQLInt}
    }
})

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

3. Определите типы запросов и мутаций. Схема должна иметь эти интерфейсы запроса и мутатора для взаимодействия с данными. Когда вы определяете интерфейс, вы можете указать несколько конечных точек внутри типа.

  • Тип запроса ( 2 метода )
const query = new graphql.GraphQLObjectType({
    name: 'vehicleQuery',
    fields: {
        vehicle: {
            type: new graphql.GraphQLList(VehicleSchema),
            args: {
                _id: {type: graphql.GraphQLString},
                carname: {type: graphql.GraphQLString}
            },
            resolve: (_, {_id, carname}) => {
                let where;
                if (_id){
                    where = {_id: _id};
                }else if (carname){
                    where = {carname: carname};
                }else{
                    where = {};
                }
                return vehicle.find(where);
            }
        },
        getByCapacity: {
            type: new graphql.GraphQLList(VehicleSchema),
            args: {
                capacity: {type: graphql.GraphQLInt}
            },
            resolve: (_, {capacity}) => {
                let where;
                if (capacity){
                    where = {engineCapacity: { $lt: capacity }};
                }else{
                    where = {};
                }
                return vehicle.find(where);
            }
        }
    }
})
  1. Запрос автомобиля. вы можете передать идентификатор объекта и запросить его (имитирует GetById в REST) ​​или передать имя автомобиля и получить результат (имитирует GetByName) или ничего не передавать и получить все результаты (имитирует GetAll).
  2. Запросы по емкости меньше включенной емкости. Я включил этот метод, чтобы показать, как мы можем вызывать несколько конечных точек в одном запросе. Подробнее об этом далее в этой статье.
  • Тип мутации ( 2 метода )

Код ниже содержит две конечные точки для создания и удаления автомобилей из базы данных.

const mutation = new graphql.GraphQLObjectType({
    name: 'vehicleMutations',
    fields: {
        create: {
            type: VehicleSchema,
            args: {
                carname: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
                year: {type: new graphql.GraphQLNonNull(graphql.GraphQLInt)},
                transmission: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
                fuelType: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
                engineCapacity: {type: new graphql.GraphQLNonNull(graphql.GraphQLInt)},
            },
            resolve: (_, {carname, year, transmission, fuelType, engineCapacity}) => {
                let v = new vehicle({carname, year, transmission, fuelType, engineCapacity});
                return v.save()
            }
        },
        delete: {
            type: VehicleSchema,
            args: {
                _id: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)}
            },
            resolve: (_, {_id}) => {
                return vehicle.findOneAndRemove(_id);
            }
        }
    }
})

В методе создания мы указываем аргументы, а затем после сохранения в монго возвращаем обещание монго. То же самое в методе удаления. Получите идентификатор из аргумента и удалите из монго, затем верните обещание.

Наконец, экспортируйте схему GraphQL с интерфейсами запросов и мутаций.

module.exports = new graphql.GraphQLSchema({
    query,
    mutation
})

Окончательный VehicleSchemaGQL.js должен выглядеть так.

const graphql = require('graphql');
const vehicle = require('./../model/vehicle');

const VehicleSchema = new graphql.GraphQLObjectType({
    name: 'vehicle',
    description: 'a vehicle to be sold',
    fields: {
        _id: {type: graphql.GraphQLString},
        carname: {type: graphql.GraphQLString},
        year: {type: graphql.GraphQLInt},
        transmission: {type: graphql.GraphQLString},
        fuelType: {type: graphql.GraphQLString},
        engineCapacity: {type: graphql.GraphQLInt}
    }
})

const query = new graphql.GraphQLObjectType({
    name: 'vehicleQuery',
    fields: {
        vehicle: {
            type: new graphql.GraphQLList(VehicleSchema),
            args: {
                _id: {type: graphql.GraphQLString},
                carname: {type: graphql.GraphQLString}
            },
            resolve: (_, {_id, carname}) => {
                let where;
                if (_id){
                    where = {_id: _id};
                }else if (carname){
                    where = {carname: carname};
                }else{
                    where = {};
                }
                return vehicle.find(where);
            }
        },
        getByCapacity: {
            type: new graphql.GraphQLList(VehicleSchema),
            args: {
                capacity: {type: graphql.GraphQLInt}
            },
            resolve: (_, {capacity}) => {
                let where;
                if (capacity){
                    where = {engineCapacity: { $lt: capacity }};
                }else{
                    where = {};
                }
                return vehicle.find(where);
            }
        }
    }
})

const mutation = new graphql.GraphQLObjectType({
    name: 'vehicleMutations',
    fields: {
        create: {
            type: VehicleSchema,
            args: {
                carname: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
                year: {type: new graphql.GraphQLNonNull(graphql.GraphQLInt)},
                transmission: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
                fuelType: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
                engineCapacity: {type: new graphql.GraphQLNonNull(graphql.GraphQLInt)},
            },
            resolve: (_, {carname, year, transmission, fuelType, engineCapacity}) => {
                let v = new vehicle({carname, year, transmission, fuelType, engineCapacity});
                return v.save()
            }
        },
        delete: {
            type: VehicleSchema,
            args: {
                _id: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)}
            },
            resolve: (_, {_id}) => {
                return vehicle.findOneAndRemove(_id);
            }
        }
    }
})

module.exports = new graphql.GraphQLSchema({
    query,
    mutation
})

Теперь сложная часть почти закончена. Импортируйте схему GraphQL в app.js и библиотеки GraphQL, определите веб-интерфейс для GraphQL. Сделаем и это.

Определить конечные точки службы GraphQL

  1. Сначала импортируйте экспресс-библиотеку graphql, а затем импортируйте только что созданную схему. Вы можете разместить этот код ниже импорта mongodb в app.js
const graphqlHTTP = require('express-graphql');
const schema = require('./graphSchema/vehicleSchemaGQL');

2. Определите конечную точку службы GraphQL

app.use('/graphql', graphqlHTTP({
    schema,
    graphiql: true
}))

Первый аргумент — это определенная схема. Второй аргумент — если мы хотим, чтобы графический пользовательский интерфейс тестировал запросы. Это не обязательно, но приятно иметь возможность протестировать материал. В производстве отключите его, указав false в качестве значения. Вы можете просмотреть пользовательский интерфейс с http://localhost:3000/graphql в веб-браузере.

Также обратите внимание, что обработчик маршрута похож не на get/post/del, а на «app.use». Этот интерфейс принимает запросы GET и POST.

Теперь окончательный файл app.js должен выглядеть примерно так.

const express = require('express');
const app = express();
const mongoose = require('mongoose');

//connect to mongodb...
mongoose.connect(`mongodb://127.0.0.1/vehicledb`);
mongoose.connection.on('error', err => console.error('FAILED to connect to mongodb instance.', err));
mongoose.connection.once('open', () => console.log('Connected to mongodb instance.'));

const graphqlHTTP = require('express-graphql');
const schema = require('./graphSchema/vehicleSchemaGQL');

app.get('/', (req, res)=>{
    res.end("graphql-vehicleshop is running on 3000 port...");
})

app.use('/graphql', graphqlHTTP({
    schema,
    graphiql: true
}))

app.listen('3000', ()=>{
    console.log("Server is starting on 3000 port...")
})

На этом настройка проекта завершена. Теперь давайте протестируем это!

Тестирование API

Если все правильно при запуске приложения node. Вы можете либо получить доступ через пользовательский интерфейс с http://localhost:3000/graphql, либо использовать POSTMAN, например клиент rest, или использовать клиент REST с поддержкой GraphQL, например Insomnia, который я настоятельно рекомендую. Я имею в виду серьезно, клиент Insomnia настолько хорош!

Но неважно, давайте проверим оба!

Пользовательский интерфейс GraphiQL

Это редактор, который вы получите при запуске. Вы можете вводить запросы на левой панели и выполнять их. Давайте сделаем некоторые.

Вставить транспортное средство

mutation {
create(carname: “Ferrari 488 GTB”, year:2017, transmission:”dual clutch auto”, fuelType:”petrol”, engineCapacity:4000) {
_id,
carname,
year,
transmission,
fuelType,
engineCapacity
}
}

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

Вы выполняете, нажимая кнопку воспроизведения на левой стороне, и ответ отображается на правой стороне. Добавьте несколько элементов, используя это.

Получить все транспортные средства

Запрос сам объясняет это.. Получите все транспортные средства, но верните только значения поля carname, year и enginecapacity.

Получить по имени

Получить по идентификатору

Выполнение нескольких запросов в одном запросе.

Это одна из ключевых особенностей GraphQL. Смотрите здесь, мы запросили обе доступные конечные точки, и ответ действительно включает оба ответа. В REST нам пришлось бы делать 2 запроса или создавать комбинированные конечные точки, но здесь все готово к использованию без дополнительных затрат на разработку. Разве это не грандиозно!

И, как вы видели, для всего этого открыта только одна конечная точка. Что довольно невероятно, учитывая, что если бы мы использовали конечные точки REST, то только для этих нескольких задач было бы около 5–6 разных конечных точек.

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

Веб-интерфейс GraphQL

Что делать, если вы не хотите использовать пользовательский интерфейс. Самый простой способ — использовать REST-клиент Insomnia со встроенной поддержкой GraphQL, который показан ниже.

Или вы можете использовать обычный почтальон, такой как REST-клиент, который не поддерживает GraphQL через HTTP. Для получения дополнительной информации взгляните на этот документ.

Чтобы отправить через HTTP, вы должны отформатировать запрос в JSON без изменения URL-адреса, а затем отправить через запрос POST.

ex:

{“query”:”query {\n vehicle(carname:\”Nissan GTR\”) {\n carname,\n year,\n engineCapacity\n }\n}”}

Вот и все. Довольно длинная статья. Но я верю, что это будет полезно. Было действительно интересно работать с GraphQL для разнообразия, и я думаю, что со временем это будет продолжаться, и я с нетерпением жду этого. До скорого. Прощай! 😄