Это руководство является второй частью серии, цель которой - помочь вам развернуть полнофункциональное полнофункциональное приложение.
- Часть 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
orfalse
.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 как одностраничное приложение.