Автор оригинала: Siegfried Grimbeek.
В этом руководстве является частью двух из четырех частей серии, которая стремится отвезти вас с нуля для развертывания полностью Функциональное приложение полного стека.
- Часть 1: Как построить пылающие API быстрого отдыха с Node.js, MongoDB, Castify и Swagger
- Часть 2: Как построить пылающий Fast GraphQL API с Node.js, mongoDb, castify и graphql! (Вы здесь.)
- Часть 3: Муфта Vue.js с Graphql api Отказ
- Часть 4: Развертывание Graphql api и Vue.js Frontend Application Отказ
Первая часть серии доступна здесь И исходный код для приложения можно найти здесь Отказ
В этой части мы пересмотрим модели , контроллеры и маршруты от части один, а затем интегрировать Graphql в приложение. Как бонус, мы также будем использовать Faker.js создать некоторые поддельные данные и семена база данных Отказ
Вступление:
Graphql Является ли язык запросов для API и время выполнения для выполнения этих запросов с вашими существующими данными.
Каждый Graphql Запрос проходит через три этапа: запросы проанализированы и выполнены.
Graphql Обеспечивает полное и понятное описание данных в вашем API, дает клиентам возможность просить именно то, что им нужно, облегчает развитие API со временем и обеспечивает мощные инструменты разработчика. Узнать больше Отказ
Предпосылки …
Если вы завершили первую часть этой серии, вы должны быть до скорости с новичком/промежуточным JavaScript Знание, Node.js, застегивает. JS и Монгодб (мангуст).
Чтобы следовать, вам нужно будет завершить Часть одна из этой серии или схватить код из Гит , хотя я очень рекомендую хотя бы снимать Часть одна Отказ
Давайте начнем!
Клонировать репо для части детали (пропустите этот шаг, если вы следите за частью, и вы продолжаете свой собственный код), открыв свой Терминал, Навигация на ваш каталог проекта и выполняя каждую из следующих строк кода:
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-контрольные обновления », а затем мы используем этот пакет для автоматического обновления нашего Package.json Файл с последними версиями пакета, а затем мы устанавливаем/обновите все наши NPM модули Бег Установка NPM .
Это делается, чтобы все завершили учебное пособие, работают с теми же версиями пакета.
Refactor наш сервер и начните приложение!
Как и во всех программных решениях, поскольку решение растет, разработчики часто нужно пересмотр и рефакторист код.
В SRC Каталог мы создадим новый файл под названием server.js :
cd src touch server.js
Добавьте следующий код кода на server.js файл:
// Require the fastify framework and instantiate it
const fastify = require('fastify')({
logger: true
})
// Require external modules
const mongoose = require('mongoose')
// Connect to DB
mongoose
.connect('mongodb://localhost/mycargarage')
.then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err))
module.exports = fastifyТеперь мы извлекли логику, которая начинает Сервер к server.js Файл, позволяющий нам повторно использовать этот код по всему проекту.
Далее нам нужно обновить наше index.js Файл в SRC Каталог:
// Import Server
const fastify = require('./server.js')
// Import Routes
const routes = require('./routes')
// Import Swagger Options
const swagger = require('./config/swagger')
// Register Swagger
fastify.register(require('fastify-swagger'), swagger.options)
// Loop over each route
routes.forEach((route, index) => {
fastify.route(route)
})
// Run the server!
const start = async () => {
try {
await fastify.listen(3000, '0.0.0.0')
fastify.swagger()
fastify.log.info(`server listening on ${fastify.server.address().port}`)
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
}
start()Мы пересматриваем index.js Файл, как только мы настроим и настроем Graphql.
Начать Застегивайте Сервер, запустив следующий код в вашем Терминал :
npm start
Обратите внимание, что на данный момент нет настройки маршрута по умолчанию, навигации к http://localhost: 3000/ приведет к серверу, возвращающей ошибку 404, которая правильная.
Запустите MongoDB и обновите модели
Давайте расширим существующую модель, чтобы также включить Услуги и Владельцы. Приведенная ниже диаграмма ниже демонстрирует отношения между коллекциями:
- Один автомобиль может иметь один владелец.
- Один владелец может иметь много машин.
- Один автомобиль может иметь много услуг.
Revisit Car.js Файл в модели каталог и обновить его следующим образом:
// External Dependancies
const mongoose = require("mongoose")
const ObjectId = mongoose.Schema.Types.ObjectId
const carSchema = new mongoose.Schema({
title: String,
brand: String,
price: String,
age: Number,
owner_id: ObjectId
})
module.exports = mongoose.model("Car", carSchema)Создайте два новых файла в модели каталог, Quare.js и Service.js и добавьте следующий код в файлы соответственно:
Quire.js.
// External Dependancies
const mongoose = require('mongoose')
const ownerSchema = new mongoose.Schema({
firstName: String,
lastName: String,
email: String
})
module.exports = mongoose.model('Owner', ownerSchema)Service.js.
// External Dependancies
const mongoose = require("mongoose")
const ObjectId = mongoose.Schema.Types.ObjectId
const serviceSchema = new mongoose.Schema({
car_id: ObjectId,
name: String,
date: String
})
module.exports = mongoose.model("Service", serviceSchema)
view rawService.js hosted with ❤ by GitHubВ приведенном выше Кодексе нет новых концепций. Мы только что создали стандартные Мангусты схемы , как с Car.js модель.
Пересмотрите автомобильный контроллер и создайте дополнительные контроллеры
Есть несколько небольших изменений в carcontroller.js Так перемещайтесь на Контроллеры Справочник и обновите свой файл согласно ниже:
// External Dependancies
const boom = require('boom')
// Get Data Models
const Car = require('../models/Car')
// Get all cars
exports.getCars = async () => {
try {
const cars = await Car.find()
return cars
} catch (err) {
throw boom.boomify(err)
}
}
// Get single car by ID
exports.getSingleCar = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const car = await Car.findById(id)
return car
} catch (err) {
throw boom.boomify(err)
}
}
// Add a new car
exports.addCar = async req => {
try {
const car = new Car(req)
const newCar = await car.save()
return newCar
} catch (err) {
throw boom.boomify(err)
}
}
// Update an existing car
exports.updateCar = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const updateData = req.params === undefined ? req : req.params
const update = await Car.findByIdAndUpdate(id, updateData, { new: true })
return update
} catch (err) {
throw boom.boomify(err)
}
}
// Delete a car
exports.deleteCar = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const car = await Car.findByIdAndRemove(id)
return car
} catch (err) {
throw boom.boomify(err)
}
}Создайте два новых файла в Контроллеры каталог, servicecontroller.js и Serverercontroller.js и добавьте следующий код в файлы соответственно:
servicecontroller.js.
// External Dependancies
const boom = require('boom')
// Get Data Models
const Service = require('../models/Service')
// Get single service ID
exports.getSingleService = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const service = await Service.findById(id)
return service
} catch (err) {
throw boom.boomify(err)
}
}
// Get single car's services
exports.getCarsServices = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const services = await Service.find({ car_id: id })
return services
} catch (err) {
throw boom.boomify(err)
}
}Serverercontroller.js.
// External Dependancies
const boom = require('boom')
// Get Data Models
const Owner = require('../models/Owner')
const Car = require('../models/Car')
// Get all owners
exports.getOwner = async () => {
try {
const owners = await Owner.find()
return owners
} catch (err) {
throw boom.boomify(err)
}
}
// Get single owner by ID
exports.getSingleOwner = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const owner = await Owner.findById(id)
return owner
} catch (err) {
throw boom.boomify(err)
}
}
// Get single owner's cars
exports.getOwnersCars = async req => {
try {
const id = req.params === undefined ? req.id : req.params.id
const cars = await Car.find({ owner_id: id })
return cars
} catch (err) {
throw boom.boomify(err)
}
}Самое большое изменение контроллеров – это то, как мы получаем параметры:
const id = req.params === undefined ? req.id : req.params.id const updateData = req.params === undefined ? req : req.params
Вышеуказанный код называется « условный (тройной) оператор “ и используется в качестве сокращения для следующего, если заявление:
let id
if (req.params === undefined) {
id = req.id
} else {
id = req.params.id
}Мы используем Тернарный оператор Для размещения запросов от обоих Отдых API и Graphql api , поскольку у них немного другая реализация.
Время для семян базы данных с помощью поддельных данных!
В SRC Каталог Давайте создадим новый каталог и файл, выполнив следующий код:
mkdir helpers touch seed.js
Добавьте следующий код в Seed.js файл:
// Import external dependancies
const faker = require('faker')
const boom = require('boom')
// Import internal dependancies
const fastify = require('../server.js')
// Fake data
const cars = [
{
name: 'Tesla',
models: ['S', 'E', 'X', 'Y']
},
{
name: 'Mercedes',
models: ['GLA', 'GLC', 'GLE', 'GLS']
},
{
name: 'BMW',
models: ['X4', 'Z3', 'M2', '7']
},
{
name: 'Audi',
models: ['A1', 'A3', 'A4', 'A5']
},
{
name: 'Ford',
models: ['Fiesta', 'Focus', 'Fusion', 'Mustang']
}
]
const serviceGarages = ['A++ Auto Services', "Gary's Garage", 'Super Service', 'iGarage', 'Best Service']
// Get Data Models
const Car = require('../models/Car')
const Owner = require('../models/Owner')
const Service = require('../models/Service')
// Fake data generation functions
const generateOwnerData = () => {
let ownerData = []
let i = 0
while (i < 50) {
const firstName = faker.fake('{{name.firstName}}')
const lastName = faker.fake('{{name.lastName}}')
const email = faker.fake(`${firstName.toLowerCase()}.${lastName.toLowerCase()}@gmail.com`)
const owner = {
firstName,
lastName,
email
}
ownerData.push(owner)
i++
}
return ownerData
}
const generateCarData = ownersIds => {
let carData = []
let i = 0
while (i < 1000) {
const owner_id = faker.random.arrayElement(ownersIds)
const carObject = faker.random.arrayElement(cars)
const title = faker.random.arrayElement(carObject.models)
const price = faker.random.number({ min: 5000, max: 30000 })
const age = faker.random.number({ min: 2, max: 10 })
const car = {
owner_id,
brand: carObject.name,
title,
price,
age
}
carData.push(car)
i++
}
return carData
}
const generateServiceData = carsIds => {
let serviceData = []
let i = 0
while (i < 5000) {
const car_id = faker.random.arrayElement(carsIds)
const name = faker.random.arrayElement(serviceGarages)
const date = faker.fake('{{date.past}}')
const service = {
car_id,
name,
date
}
serviceData.push(service)
i++
}
return serviceData
}
fastify.ready().then(
async () => {
try {
const owners = await Owner.insertMany(generateOwnerData())
const ownersIds = owners.map(x => x._id)
const cars = await Car.insertMany(generateCarData(ownersIds))
const carsIds = cars.map(x => x._id)
const services = await Service.insertMany(generateServiceData(carsIds))
console.log(`
Data successfully added:
- ${owners.length} owners added.
- ${cars.length} cars added.
- ${services.length} services added.
`)
} catch (err) {
throw boom.boomify(err)
}
process.exit()
},
err => {
console.log('An error occured: ', err)
process.exit()
}
)Давайте сломаем эту гору код:
Сначала мы импортируем два внешних библиотеках, Faker.js который используется для генерации поддельных данных и Бум , который используется для броска объектов ошибок HTTP.
Тогда мы импортируем server.js Файл, который раскрутит экземпляр нашего сервера, позволяющий нам взаимодействовать с модели Отказ
Затем мы объявляем две массивы с поддельными данными, Автомобили и СервисГарги Отказ
Тогда мы импортируем модели и объявить три функции ( GenerateownerData , GenerateCardata , generatevericedata ) который каждый возвращает массив объектов с владелец , Автомобиль и Сервис данные соответственно.
Однажды Veafify.js Экземпляр готов, мы используем Мангуст insertmany () Функция Чтобы вставить генерируемые массивы в базу данных. Затем функция возвращает массив объектов, содержащих исходные данные объекта и IDS из каждой записи.
Мы используем Карта JavaScript Функция для создания массива IDS. владельцы и Автомобили массивы Мы используем владельцы Массив для создания данных автомобиля и мы используем Carsids Массив при генерировании данных обслуживания они передаются в соответствующие функции, а затем значения случайным образом выбираются случайным образом.
Наконец, нам нужно установить Faker.js Пакет и добавьте задачу семян нашим Package.json файл.
Мы можем добавить Faker.js Пакет, навигацию к Корневой каталог и выполнение следующего кода:
npm i faker -D
Затем мы добавим следующее в Package.json файл:
...
"scripts": {
...
"seed": "node ./src/helpers/seed.js"
},
...Вот и все! Теперь мы можем запустить сценарий нашего посева в корневом каталоге проекта со следующим кодом:
npm run seed
Если вы используете Монгодб Компас (Вы должны), вы увидите данные в вашей базе данных:
Установка GraphQL, настройка и тестирование
Давайте начнем при навигации на Корневой каталог и выполнение следующего кода:
npm i fastify-gql graphql
Вышеуказанные установки График и Застегивайте Barebone GraphQL адаптер.
Перейдите к 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')С вышеуказанным кодом нам требуется Застегивайте адаптер GraphQL, Импорт Схема и зарегистрируйте График адаптер с Застегивать.
Мы регистрируем Схема и включить Graphiql, в браузере IDE для изучения График .
Перейдите к Схема каталог и открыть index.js Файл и добавьте следующий код котельной:
// Import External Dependancies
const graphql = require('graphql')
// Destructure GraphQL functions
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLID,
GraphQLList,
GraphQLNonNull
} = graphql
// Import Controllers
const carController = require('../controllers/carController')
const ownerController = require('../controllers/ownerController')
const serviceController = require('../controllers/serviceController')
// Define Object Types
const carType = new GraphQLObjectType({
name: 'Car',
fields: () => ({})
})
const ownerType = new GraphQLObjectType({
name: 'Owner',
fields: () => ({})
})
const serviceType = new GraphQLObjectType({
name: 'Service',
fields: () => ({})
})
// Define Root Query
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
car: {},
cars: {},
owner: {},
service: {}
}
})
// Define Mutations
const Mutations = new GraphQLObjectType({
name: 'Mutations',
fields: {
addCar: {
type: carType,
args: {},
async resolve(args) {
return ''
}
},
editCar: {
type: carType,
args: {},
async resolve(args) {
return ''
}
},
deleteCar: {
type: carType,
args: {},
async resolve(args) {
return ''
}
}
}
})
// Export the schema
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutations
})Давайте пройдем через вышеуказанный код:
Нам требуется главная Graphql Пакет и использование Разрушение JavaScript Чтобы получить необходимую Graphql Функции ( graphqlschema , Graphqlobjecttype , Graphqlstring , График , Graphqlid , Graphqllist и Graphqlnonnull . ).
Мы импортируем наши три Контроллеры ( Carcontroller , Vereercontroller и ServiceController ).
Мы объявляем Cartype С Soalertype и Сервис Типы объектов GraphQL , которые являются функциями, которые принимают объект как параметр, с помощью имя и а Поля ключ.
Эти функции используются для определения наших Graphql Схема, похожая на Мангуста модели определены ранее.
Поля могут вернуть конкретное Тип и Методы которые принимают аргументы. Узнайте больше о типах объектов Отказ
Тогда мы объявляем Рулество который также является Тип объекта graphql и находится на верхнем уровне каждого Graphql сервер. Это представляет все возможные точки входа в API GraphQL. Узнайте больше о корневых полях и резольвестах Отказ
Затем мы объявляем наши Мутации , которые используются для Изменить данные. Хотя любой запрос может быть реализован для изменения данных, операций, которые приводятся изменения, которые могут быть отправлены явно через мутацию Отказ Узнайте больше о мутациях Отказ
Наконец мы экспортируем Graphqlschema.
Теперь, когда у нас есть наш шаблон настройки, мы можем начать заполнять Типы объектов , Root Query и Мутации Отказ
Обратите внимание, что есть Mongoose для graphql. Доступны генераторы схем, но для учебных целей мы вручную создаем схему.
Давайте обновим Cartype Тип объекта следующее:
const carType = new GraphQLObjectType({
name: 'Car',
fields: () => ({
_id: { type: GraphQLID },
title: { type: GraphQLString },
brand: { type: GraphQLString },
price: { type: GraphQLString },
age: { type: GraphQLInt },
owner_id: { type: GraphQLID },
owner: {
type: ownerType,
async resolve(parent, args) {
return await ownerController.getSingleOwner({ id: parent.owner_id })
}
},
services: {
type: new GraphQLList(serviceType),
async resolve(parent, args) {
return await serviceController.getCarsServices({ id: parent._id })
}
}
})
})Давайте погрузиться глубже в Graphql Функции, начиная с Скаляры Типы в График :
Graphql Поставляется с набором скалярных типов по умолчанию из коробки:
Int: Подписанное 32-битное целое число.ГрафикПлавать: Подписанная двойная точность плавающего значения.Graphqlfloat.Строка: Последовательность символов UTF-8.Graphqlstring.Логический:правдаилиложный.Graphqlboolean.ID: Тип STALAR ID представляет собой уникальный идентификатор, который часто используется для разведения объекта или в качестве ключа для кэша. Тип идентификатора сериализуется таким же образом, как строка; Однако определяя это какIDозначает, что он не предназначен для чтения человека.Haphqlid.
владелец и Сервис Поля, где это становится интересным. Эти поля не определены как Скалярные типы Как и остальные – вместо этого их Тип ссылается на Soalertype и Сервис что мы создали и еще не заполнены.
Второй аргумент, который мы передаем в владелец и Сервис Поля – Resolver Функции.
Функции или методы Resolver являются функциями, которые разрешает значение Для типа или поля в схеме
Резольверы тоже могут быть асинхронными! Они могут разрешать значения из другого REST API, База данных, кэш, константы и т. Д.
Для того, чтобы создать отношения между различными типами, мы передаем _id и suder_id значения в соответствующие функции контроллера.
По сути, мы запрашиваем подробности владельца вместе с деталями автомобиля:
return await userController.getSingleOwner({ id: parent.owner_id })И детали всех услуг, связанных с автомобилем:
return await serviceController.getCarsServices({ id: parent._id })Чтобы вернуть список или массив с Graphql, Мы используем Graphqllist. . Здесь Хорошо в глубине учебника по поводу использования массивов в Graphql Схема, но это действительно просто: всякий раз, когда нам нужен массив, мы будем использовать Graphqllist функция.
Давайте обновим Soalertype и Сервис со следующим кодом:
владелец
const ownerType = new GraphQLObjectType({
name: 'Owner',
fields: () => ({
_id: { type: GraphQLID },
firstName: { type: GraphQLString },
lastName: { type: GraphQLString },
email: { type: GraphQLString },
cars: {
type: new GraphQLList(carType),
async resolve(parent, args) {
return await ownerController.getOwnersCars({ id: parent._id })
}
}
})
})Тип Обслуживания
const serviceType = new GraphQLObjectType({
name: 'Service',
fields: () => ({
_id: { type: GraphQLID },
car_id: { type: GraphQLID },
name: { type: GraphQLString },
date: { type: GraphQLString },
car: {
type: carType,
async resolve(parent, args) {
return await carController.getSingleCar({ id: parent.car_id })
}
}
})
})Выше два Типы объектов очень похожи на Cartype Отказ Вы можете заметить шаблон между разными Типы объектов и их отношения.
Теперь мы можем заполнить Рулество root со следующим кодом:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
car: {
type: carType,
args: { id: { type: GraphQLID } },
async resolve(parent, args) {
return await carController.getSingleCar(args)
}
},
cars: {
type: new GraphQLList(carType),
async resolve(parent, args) {
return await carController.getCars()
}
},
owner: {
type: ownerType,
args: { id: { type: GraphQLID } },
async resolve(parent, args) {
return await ownerController.getSingleOwner(args)
}
},
service: {
type: serviceType,
args: { id: { type: GraphQLID } },
async resolve(parent, args) {
return await serviceController.getSingleService(args)
}
}
}
})В приведенном выше Кодексе нет новых концепций, но имейте в виду, что Рулество Запрос – это точка входа на все запросы на API GraphQL. Итак, из вышесказанного мы видим, что мы можем напрямую запускать следующие запросы:
- Получить все машины
- Получить одну машину
- Получить один владелец
- Получить один сервис
Давайте откроем Graphiql Пользовательский интерфейс и построить некоторые запросы: http://localhost: 3000/graphiql.html
Запросы вводятся слева, результаты находятся в середине, а проводник документации находится справа.
Проводник документации может быть использован для изучения всего графа до скалярного уровня. Это очень полезно при построении запросов.
Язык, используемый для построения запросов, напоминающих JSON. Это чит лист это отличная ссылка.
Ниже демонстрирует почему График Это так круто :
В приведенном выше примере мы используем Автомобили Root Query для отображения списка всех автомобилей, их владельцев и их услуг.
У нас есть одна окончательная тема для адреса, и это Мутации Отказ Давайте обновим Мутации со следующим кодом:
const Mutations = new GraphQLObjectType({
name: 'Mutations',
fields: {
addCar: {
type: carType,
args: {
title: { type: new GraphQLNonNull(GraphQLString) },
brand: { type: new GraphQLNonNull(GraphQLString) },
price: { type: GraphQLString },
age: { type: GraphQLInt },
owner_id: { type: GraphQLID }
},
async resolve(parent, args) {
const data = await carController.addCar(args)
return data
}
},
editCar: {
type: carType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) },
title: { type: new GraphQLNonNull(GraphQLString) },
brand: { type: new GraphQLNonNull(GraphQLString) },
price: { type: new GraphQLNonNull(GraphQLString) },
age: { type: new GraphQLNonNull(GraphQLInt) },
owner_id: { type: GraphQLID }
},
async resolve(parent, args) {
const data = await carController.updateCar(args)
return data
}
},
deleteCar: {
type: carType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) }
},
async resolve(parent, args) {
const data = await carController.deleteCar(args)
return data
}
}
}
})Как и раньше, мы объявляем наши Тип объекта , укажите имя и Поля Отказ
Мутация состоит из Тип , args и Async решить функция. решить Функция передает args для контроллера, который возвращает результат мутации.
Вы уже закодировали полностью функционал Отдых API и полностью функциональный API GraphQL.
Нет никаких правил, указанных, что нужно использовать исключительно Отдых или исключительно Graphql. В некоторых проектах лучшее решение может быть смесью обоих. Это действительно определяется на проектной основе.
Вы можете скачать исходный код из Git здесь Отказ
Что следующее?
В следующем уроке мы будем потреблять наши График API с Vue.js Frontend как приложение одной страницы!
Оригинал: “https://www.freecodecamp.org/news/how-to-build-a-blazing-fast-graphql-api-with-node-js-mongodb-and-fastify-77fd5acd2998/”