Это руководство является второй частью серии, цель которой - помочь вам развернуть полнофункциональное полнофункциональное приложение.

  • Часть 1. Как создать невероятно быстрые REST API с помощью Node.js, MongoDB, Fastify и Swagger
  • Часть 2: Как создать молниеносный API GraphQL с помощью GraphQL, Node.js, MongoDB и Fastify (вы здесь.)
  • Часть 3: Соединение Vue.js с GraphQL API
  • Часть 4. Развертывание GraphQL API и Vue.js интерфейсного приложения

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

В этой части мы вернемся к моделям, контроллерам и маршрутам из Части 1, а затем интегрируем GraphQL в приложение. В качестве бонуса мы также будем использовать Faker.js для создания поддельных данных и заполнения базы данных.

Вступление

GraphQL - это язык запросов для API и среда выполнения для выполнения этих запросов с вашими существующими данными.

Каждый запрос GraphQL проходит три фазы: запросы анализируются, проверяются и выполняются.

GraphQL предоставляет полное и понятное описание данных в вашем API, дает клиентам возможность запрашивать именно то, что им нужно, упрощает развитие API с течением времени и предоставляет мощные инструменты разработчика.

Предпосылки

Если вы завершили первую часть этой серии, вы должны быть в курсе начального / среднего уровня JavaScript, Node.js, Fastify.JS и MongoDB (Mongoose).

Чтобы продолжить, вам нужно будет выполнить Часть 1 этой серии или взять код с GitHub - хотя я настоятельно рекомендую хотя бы бегло просмотреть часть 1.

Давайте начнем

Клонируйте репозиторий для Части 1 (пропустите этот шаг, если вы следовали Части 1 и продолжаете работать со своим собственным кодом), открыв терминал , перейдя в каталог проекта и выполнение каждой из следующих строк кода:

git clone https://github.com/siegfriedgrimbeek/fastify-api.git
cd fastify-api

Итак, теперь, когда у нас есть копия базы кода, мы обновим наши пакеты и package.json файл, запустив следующий код:

sudo npm i -g npm-check-updates
ncu -u
npm install

Сначала мы глобально устанавливаем пакет npm npm-check-updates ,, а затем используем этот пакет для автоматического обновления нашего package.json файла последними версиями пакета. Затем мы устанавливаем / обновляем все наши модули npm, запустив npm install.

Это сделано для того, чтобы все, проходящие обучение, работали с одинаковыми версиями пакетов.

Выполните рефакторинг нашего сервера и запустите приложение

Как и в случае со всеми программными решениями, по мере роста решения разработчикам часто приходится пересматривать и реорганизовывать код.

В каталоге src мы создадим новый файл с именем server.js:

cd src
touch server.js

Добавьте в файл server.js следующий код:

Теперь мы извлекли логику, запускающую сервер, в файл server.js, что позволяет нам повторно использовать этот код во всем проекте.

Затем нам нужно обновить наш index.js файл в каталоге src:

Мы вернемся к файлу index.js после того, как настроим и настроим GraphQL .

Запустите сервер Fastify , запустив следующий код в своем терминале:

npm start

Обратите внимание, что на данный момент нет настройки маршрута по умолчанию. Переход к http: // localhost: 3000 / приведет к тому, что сервер вернет ошибку 404, что правильно.

Запустите MongoDB и обновите модели

Давайте расширим существующую модель, включив также службы и владельцев . На схеме ниже показаны отношения между коллекциями:

  • У одной машины может быть один владелец
  • У одного хозяина может быть много машин
  • Одна машина может иметь много услуг

Вернитесь к файлу Car.js в каталоге models и обновите его следующим образом:

Создайте два новых файла в каталоге models, Owner.js и Service.js, и добавьте к файлам следующий код соответственно:

Owner.js

Service.js

В приведенном выше коде нет новых концепций. Мы только что создали стандартные схемы Mongoose, как в модели Car.js.

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

В файл carController.js внесены небольшие изменения. Итак, перейдите в каталог controllers и обновите свой файл, как показано ниже:

Создайте два новых файла в каталоге controllers, serviceController.js и ownerController.js, и добавьте к файлам следующий код соответственно:

serviceController.js

ownerController.js

Самое большое изменение в контроллерах - это то, как мы получаем параметры:

const id = req.params === undefined ? req.id : req.params.id
const updateData = req.params === undefined ? req : req.params

Приведенный выше код называется условным (тернарным) оператором и используется как сокращение для следующего оператора if:

let id
if (req.params === undefined) {
id = req.id
} else {
id = req.params.id
}

Мы используем тернарный оператор для обработки запросов как от REST API, так и от GraphQL API, поскольку они имеют несколько разные реализации.

Пора заполнить базу данных фальшивыми данными

В каталоге src создадим новый каталог и файл, выполнив следующий код:

mkdir helpers
touch seed.js

Добавьте в файл seed.js следующий код:

Давайте разберем эту гору кода:

Сначала мы импортируем две внешние библиотеки - Faker.js, , который используется для генерации поддельных данных, и Boom, который используется для выдачи дружественных HTTP объектов ошибок.

Затем мы импортируем файл server.js, который запускает экземпляр нашего сервера, позволяя нам взаимодействовать с моделями.

Затем мы объявляем два массива с поддельными данными, cars и serviceGarages.

Затем мы импортируем models и объявляем три функции (generateOwnerData, generateCarData и generateServiceData), каждая из которых возвращает массив объектов с данными владельца, автомобиля и службы соответственно.

Когда экземпляр Fastify.js готов, мы используем Mongoose insertMany() function для вставки сгенерированных массивов в базу данных. Затем функция возвращает массив объектов, содержащий исходные данные объекта и ids каждой записи.

Мы используем функцию JavaScript map для создания массива ids для массивов владельцев и автомобилей. Мы используем массив ownersIDs для генерации данных об автомобилях, и мы используем массив carsIds при генерации служебных данных. Они передаются в соответствующие функции, а затем из них случайным образом выбираются значения.

Наконец, нам нужно установить пакет Faker.js и добавить начальную задачу в наш package.json файл.

Мы можем добавить пакет Faker.js, перейдя в корневой каталог и запустив следующий код:

npm i faker -D

Затем мы добавляем в файл package.json следующее:

...
"scripts": {
...
"seed": "node ./src/helpers/seed.js"
},
...

Вот и все. Теперь мы можем запустить наш сценарий заполнения из корневого каталога проекта со следующим кодом:

npm run seed

Если вы используете MongoDB Compass (вы должны), вы увидите данные в своей базе данных:

Установка, настройка и тестирование GraphQL

Давайте начнем с перехода в корневой каталог и выполнения следующего кода:

npm i fastify-gql graphql

Вышеупомянутый устанавливает GraphQL и адаптер GraphQL Fastify barebone.

Перейдите в каталог src и запустите следующий код:

mkdir schema
cd shema
touch index.js

Перейдите в каталог src и обновите файл index.js следующим образом:

// Import Server
const fastify = require('./server.js')
// Import external dependancies
const gql = require('fastify-gql')
// Import GraphQL Schema
const schema = require('./schema')
// Register Fastify GraphQL
fastify.register(gql, {
   schema,
   graphiql: true
})
... end here
// Import Routes
const routes = require('./routes')

С приведенным выше кодом нам требуется адаптер Fastify GraphQL, поэтому импортируйте схему и зарегистрируйте адаптер GraphQL с помощью Fastify.

Мы регистрируем схему и включаем GraphiQL, встроенную в браузер IDE для изучения GraphQL.

Перейдите в каталог schema и откройте файл index.js. Добавьте следующий шаблонный код:

Давайте рассмотрим приведенный выше код:

Нам нужен основной пакет GraphQL и мы используем деструктуризацию JavaScript для получения необходимых функций GraphQL (GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLID, GraphQLList и GraphQLNonNull).

Мы импортируем наши три controllers (carController, ownerController и serviceController).

Мы объявляем carType, ownerType и serviceType типы объектов GraphQL ,, которые представляют собой функции, которые принимают объект в качестве параметра с ключами name и fields.

Эти функции используются для определения нашей схемы GraphQL, аналогично моделям Mongoose, определенным ранее.

Поля могут возвращать определенный тип и методы, которые принимают аргументы.

Затем мы объявляем RootQuery, который также является типом объекта GraphQL и находится на верхнем уровне каждого сервера GraphQL. Он представляет все возможные точки входа в GraphQL API.

Затем мы объявляем наши Mutations, которые используются для изменения данных. Хотя любой запрос может быть реализован для изменения данных, операции, вызывающие изменения, должны отправляться явно через мутацию.

Наконец, мы экспортируем файл GraphQLSchema.

Теперь, когда у нас настроен наш шаблон, мы можем начать заполнять типы объектов, корневой запрос и мутации.

Обратите внимание, что существуют генераторы схемы Mongoose-to-GraphQL, но для целей руководства мы создадим схему вручную.

Давайте обновим тип объекта carType следующим образом:

Давайте углубимся в функции GraphQL, начав с типов Скаляры в GraphQL:

GraphQL поставляется с набором скалярных типов по умолчанию из коробки:

  • Int: 32-битное целое число со знаком. GraphQLInt
  • Float: значение с плавающей запятой двойной точности со знаком. GraphQLFloat
  • String: последовательность символов UTF ‐ 8. GraphQLString
  • Boolean: true or false. GraphQLBoolean
  • ID: скалярный тип ID представляет собой уникальный идентификатор, часто используемый для повторной выборки объекта или в качестве ключа для кеша. Тип идентификатора сериализуется так же, как строка; однако определение его как ID означает, что он не предназначен для чтения человеком. GraphQLID

Поля owner и service - вот где становится интересно. Эти поля не определены как скалярные типы, как остальные - вместо этого их type ссылаются на ownerType и serviceType, которые мы создали и еще не заполните.

Второй аргумент, который мы передаем в поля owner и service, - это функции преобразователя.

Функции или методы преобразователя - это функции, которые разрешают значение для типа или поля в схеме.

Резольверы тоже могут быть асинхронными. Они могут разрешать значения из другого REST API, базы данных, кеша, константы и т. Д. Согласно документации GraphQL:

Вы можете думать о каждом поле в запросе GraphQL как о функции или методе предыдущего типа, который возвращает следующий тип. Фактически, именно так работает GraphQL. Каждое поле каждого типа поддерживается функцией, называемой преобразователем, которая предоставляется разработчиком сервера GraphQL. При выполнении поля вызывается соответствующий преобразователь для получения следующего значения.

Если поле создает скалярное значение, такое как строка или число, выполнение завершается. Однако, если поле создает значение объекта, запрос будет содержать другой набор полей, которые применяются к этому объекту. Это продолжается до тех пор, пока не будут достигнуты скалярные значения. Запросы GraphQL всегда заканчиваются скалярными значениями.

Чтобы создать связь между различными типами, мы передаем значения _id и owner_id в соответствующие функции контроллера.

По сути, мы запрашиваем информацию о владельце вместе с данными об автомобиле:

return await userController.getSingleOwner({ id: parent.owner_id })

И подробности обо всех услугах, связанных с автомобилем:

return await serviceController.getCarsServices({ id: parent._id })

Чтобы вернуть список или массив из GraphQL, мы используем GraphQLList. Вот - отличное подробное руководство по использованию массивов в схеме GraphQL, но в любом случае это действительно просто. Когда нам понадобится массив, мы будем использовать функцию GraphQLList.

Давайте обновим ownerType и serviceType следующим кодом:

ownerType

serviceType

Вышеупомянутые два типа объектов очень похожи на carType. Вы можете заметить закономерность между различными типами объектов и их отношениями.

Теперь мы можем заполнить корень RootQuery следующим кодом:

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

  • Получите все машины
  • Получите одну машину
  • Получите единственного владельца
  • Получите единую услугу

Давайте откроем пользовательский интерфейс GraphiQL и создадим несколько запросов: http: // localhost: 3000 / graphiql.html

Запросы вводятся слева, результаты - посередине, а проводник документации - справа.

Проводник документации можно использовать для исследования всего графика до скалярного уровня. Это очень полезно при построении запросов.

Язык, используемый для построения запросов, напоминает JSON. Эта шпаргалка - отличный справочник.

Пример ниже демонстрирует, почему GraphQL такой классный :

В приведенном выше примере мы используем корневой запрос cars для отображения списка всех автомобилей, их владельцев и их услуг.

У нас есть еще одна последняя тема для обсуждения, и это mutations. Давайте обновим mutations следующим кодом:

Как и раньше, мы объявляем наш тип объекта и указываем имя и поля.

Мутация состоит из типа, аргументов и функции асинхронного разрешения. Функция разрешения передает аргументы контроллеру, который возвращает результат мутации.

Вы создали полнофункциональный REST API и полностью функциональный GraphQL API.

Нет никаких правил, согласно которым следует использовать исключительно REST или исключительно GraphQL. В некоторых проектах лучшим решением может быть сочетание того и другого. Это действительно определяется от проекта к проекту.

Вы можете скачать исходный код на GitHub здесь.

Что дальше?

В следующем руководстве мы будем использовать наш GraphQL API с интерфейсом Vue.js как одностраничное приложение.