[Из песочницы] GraphQL сервер с Koa2 и MongoDB
Тот кто знаком с GraphQL, в этой статье не узнает ничего нового. Тот кто спешит, может сразу заглянуть в готовый репозиторий GitHub. Тот, кто заинтересован и располагает хотя бы часом времени, сможет с нулевыми знаниями создать свой полностью рабочий GraphQL сервер и что важнее — разобраться, как это все работает, даже если вы не дружите с node.js.
Введение в GraphQL
GraphQL — это язык запросов, который разработал Facebook и анонсировал в 2015 году. Он позволяет получать данные с сервера, добавлять, удалять, редактировать записи и создавать приложение реального времени буквально на коленках.
Подготовка компьютера
Кто знаком с основами node.js, может смело пропустить этот раздел.
В первую очередь на рабочем компьютере нам понадобится последняя версия Node.js 7.7.4+
По завершению установки откройте консоль и напишите команду:
node -v
Консоль вывела v7.7.4+ ? Отлично.
Чтобы не путаться в консольных командах, я составлю компактную таблицу, которая поможет ориентироваться:
npm // пакетный менеджер, через который мы будем все устанавливать
i // сокращенно от install
-g // установка модуля глобально
init // ручное создание файла package.json
init -y // автоматическое создание файла package.json
-D // установка пакетов в раздел "devDependencies"
-S // установка пакетов в раздел "dependencies"
package.json:
"devDependencies" - раздел с модулями используемых при разработке
"dependencies" - раздел с модулями используемых в продакшене
Приступим к настройке проекта. Выполните по порядку команды в консоле:
// создаст рабочую директорию
mkdir test
// будет выполнять последующие команды из указанной директории
cd test
// создаст автоматически package.json в папке test
npm init -y
В процессе разработки мы сделаем много изменений, а значит будет здорово, чтобы сервер перезагружался автоматически. Для этого установим глобально пакет nodemon:
npm i -g nodemon
По умолчанию все пакеты устанавливаются локально. При глобальной установке, пакет будет доступен на любом проекте в пределах рабочего компьютера.
Настройка сервера
У вас создана папка проекта, в ней имеется файл package.json, глобально установлен nodemon , проект открыт в редакторе кода или IDE? Чудно, пока этого будет достаточно.
Откроем package.json в разделе «scripts», удалим строку «test» и добавим новую, чтобы получилось:
"scripts": {
"dev": "nodemon ./src/main.js"
},
В корне проекта создайте папку srс , а в ней 3 файла: main.js, server.js, db.js .
В файле server.js будут храниться основные настройки сервера. main.js — точка входа проекта. db.js — отдельный файл с настройками подключения к базе данных.
При написание кода, будет использоваться синтаксис es2015 (es6), поэтому понадобится Babel для компиляции es6 в es5:
// babel-preset-es2015 компилирует es6 в es5
// babel-register компилировать на ходу
// babel-plugin-transform-runtime + babel-preset-stage-3
// пригодятся позже
npm i -D babel-register babel-preset-es2015 babel-preset-stage-3 babel-plugin-transform-runtime
Создадим еще один файл в корне проекта .babelrc c кодом:
{
"presets": [
"es2015",
"stage-3"
],
"plugins": ["transform-runtime"]
}
.babelrc — это файл настроек для компилятора Babel, он используется автоматически. В main.js добавим:
// babel-register будет компилировать код из server.js
require('babel-register');
require('./server');
Установим пакет mongoose , который позволит взаимодействовать с MongoDB.
npm i -S mongoose
В файл db.js добавьте:
import mongoose from 'mongoose';
mongoose.Promise = global.Promise;
// если подключение к БД успешно, то в консоле увидим:
// 'Connected to mongo server.'
mongoose.connection.on('open', function (ref) {
console.log('Connected to mongo server.');
});
// если сервер не может подключится к БД, то выведет сообщение:
// 'Could not connect to mongo server!' + ошибки
mongoose.connection.on('error', function (err) {
console.log('Could not connect to mongo server!');
console.log(err);
});
// пример подключения к MongoDB
// mongodb://username:password@host:port/myDataBase
mongoose.connect('mongodb://localhost:27017/test');
Напомню, mongoose всего лишь мост, между сервером и базой данных. Запустить MongoDB можно в облаке или на локальной машине. Поиск в Google: free mongodb cloud поможет найти бесплатное облачный хостинг. Также обратитесь к документации mongoose.
У себя я использую локальную БД: mongodb://localhost:27017/test .
В строке подключения нет username и password, в качестве host: localhost , порт: 27017, имя базы данных: test .
Наконец, мы подошли к последнему шагу настройки сервера. Вернемся к файлу server.js и установим требуемые пакеты:
// @next - это самая последняя версия пакета
npm i -S koa koa-router@next koa-bodyparser@next graphql-server-koa
В сам файл server.js скопируем код:
// koa - node.js фреймворк на базе которого запускается сервер
// koa-router - маршрутизация на сервере
// graphql-server-koa модуль связки, чтобы подружить Koa и GraphQL
import koa from 'koa'; // koa@2
import koaRouter from 'koa-router'; // koa-router@next
import koaBody from 'koa-bodyparser'; // koa-bodyparser@next
import {graphqlKoa, graphiqlKoa} from 'graphql-server-koa';
// знакомство с schema ждет нас впереди
// db.js - файл отвечающий за подключение к MongoDB
import schema from './data/schema'
import './db'
const app = new koa();
const router = new koaRouter();
const PORT = 3000;
// koaBody is needed just for POST.
app.use(koaBody());
// POST и GET запросы будут перенаправляться в схему GraphQL
router.post('/graphql', graphqlKoa({schema: schema}));
router.get('/graphql', graphqlKoa({schema: schema}));
// инструмент для тестирования запросов localhost:3000/graphiql
router.get('/graphiql', graphiqlKoa({endpointURL: '/graphql'}));
app.use(router.routes());
app.use(router.allowedMethods());
// запуск сервера
app.listen(PORT, () => {
console.log('Server is running on', 'localhost:' + PORT);
console.log('GraphiQL dashboard', 'localhost:' + PORT + '/graphiql');
});
Настройка сервера завершена. Если вы это осилили, значит хотите увидеть результат работы. В файле server.js закомментируем три строчки кода:
//import schema from './data/schema';
//router.post('/graphql', graphqlKoa({schema: schema}));
//router.get('/graphql', graphqlKoa({schema: schema}));
Мы еще не создавали схему GraphQL, поэтому так мы избежим ошибок и сможем насладиться рабочим сервером. Запустим его:
npm run dev
Если все сделали правильно и имеется подключение к MongoDB, в консоле увидим следующее:
> nodemon ./src/main.js
[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./src/main.js`
Sever is running on localhost:3000
GraphiQL dashboard localhost:3000/graphiql
Connected to mongo server.
В консоле ошибки? Ничего страшного. Откройте оригинальный проект на Github и сверьте файлы за исключением папок data и .idea .
Если получилось запустить без ошибок, откройте в браузере:
http://localhost:3000/graphiql
Вам будет доступен графический инструмент GraphiQL для тестирования запросов. Получилось? Отлично.
Пришло время сделать небольшой перерыв. Налейте себе кофе или чай, в зависимости от того, что вам больше нравится. Позже мы продолжим говорить о самом интересном.
2 + 2 = GraphQL
GraphQL — прост и я могу это доказать.
Недавно, вы возможно прислушались моего совета и сделали себе чай или кофе. Теперь представьте, что за вас это бы сделал некая Jane Dohe.
Так, вам нужно лишь ей сказать, что именно вы хотите: чай или кофе. Jane сможет сама приготовить и принести вам. Даже если вы предпочитаете сахар и молоко в напиток, Jane возьмет у соседки сахар, а за молоком дойдет до магазина. Сама все приготовит и уже в готовом виде принесет вам, в то самое место, где вы об этом ее попросили.
У Jane есть лишь один нюанс, сама она не знает, где брать сахар и молоко, поэтому ей нужно один раз объяснить и при любой повторной просьбе она будет это делать самостоятельно.
Именно так работает GraphQL на примере Jane Dohe. Вы отправляете запрос или запросы из любого места проекта. В тоже самое место, вы получаете ответ в формате JSON. Даже если запрашиваемые данные находятся в разных базах: MongoDB, MySQL, PostgreSQL. С одним нюансом, как и Jane, прежде чем делать запрос, нужно один раз объяснить GraphQL серверу, как готовить данные и откуда их нужно брать.
Помните, когда мы комментировали три строчки кода в server.js, чтобы запустить проект? Пора раскомментировать их обратно:
import schema from './data/schema';
router.post('/graphql', graphqlKoa({schema: schema}));
router.get('/graphql', graphqlKoa({schema: schema}));
В папке src создайте папку data. В папке data создайте файл schema.js и добавьте папку user в которой нам потребуются 3 файла: queries.js , mutations.js и models.js.
Вся схема взаимосвязи будет выглядеть следующим образом:
Прежде чем углубляться, давайте разберемся. У Jane Dohe один нюанс: ей нужно объяснить, где можно взять сахар и молоко. Так вот, когда нам потребуется получить с сервера данные, добавить, изменить, удалить, то каждый случай необходимо описать отдельно.
Например, для получения данных пользователя, мы создадим отдельное поле User, которое по определенным критериям найдет и вернет одного пользователя. То же самое для массива пользователей Users. Любые операции для вывода данных в GraphQL называются — queries и будут храниться в queries.js .
Аналогично queries, операция добавление, удаление и изменение данных, называются mutations и будут храниться в mutations.js. Каждой операции будет соответствовать конкретное поле: UserCreate, UserDelete, UserEdit.
Queries и mutations имеют много общего, помимо того, что они практически идентичны — у них общая модель.
models.js — это файл в котором мы описываем схему коллекции данных, определяем имя, описываем типы, отдельно для queries и mutations.
Типы очень похожи на схему коллекции, при этом имеет три явных преимущества:
- Типы фильтруют входящие запросы. Вы можете их считать дополнительной защитой на пути к базе данных.
- Из-за того, что запросы проверяются до модели, это увеличивает производительность сервера.
- В типах можно ограничивать исходящие данные. Например, запретить получение пароля пользователя или другую важную информацию. Для этого достаточно их не указывать.
Именно из-за преимуществ, для queries и mutations будут отдельные типы.
GraphQL — Just do it!
Пришло время заполнить последние файлы с кодом.
Установите модуль для работы GraphQL сервера, который включает уже готовые пакеты для создания схемы, мутаций, запросов:
npm i -S graphql
Схема — это ядро GraphQL сервера. Она может быть только одна и содержать только по 1 параметру queries и mutations.
Добавим код в schema.js :
import {
GraphQLObjectType,
GraphQLSchema
} from 'graphql';
// импортируем queries и mutations из папки user
import UserQueries from './user/queries'
import UserMutations from './user/mutations'
// создадим GraphQL схему и заполним параметрами
// каждый параметр может содержать только один GraphQLObjectType
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query', // произвольное имя для API библитеки
fields: UserQueries // поля из файла queries.js
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: UserMutations
})
});
От схемы перейдем к models.js :
import {
GraphQLObjectType,
GraphQLInputObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLID
} from 'graphql';
import mongoose from 'mongoose';
// схема коллекции
const schema = new mongoose.Schema({
firstName: {
type: String,
},
lastName: {
type: String,
}
});
// определяем коллекцию User и подключаем к ней схему
export let UserModel = mongoose.model('User', schema);
// тип для queries
export let UserType = new GraphQLObjectType({
name: 'User',
fields: {
_id: {
type: new GraphQLNonNull(GraphQLID)
},
firstName: {
type: GraphQLString
},
lastName: {
type: GraphQLString
}
}
});
// тип для mutations
export let UserInput = new GraphQLInputObjectType({
name: "UserInput",
fields: {
firstName: {
type: GraphQLString
},
lastName: {
type: GraphQLString
}
}
});
Вспомните, мы ранее говорили о полях User и Users, для вывода пользователя и пользователей соответственно. Пора заполнить файл queries.js :
import {
GraphQLID,
GraphQLList,
GraphQLNonNull
} from 'graphql';
// импортируем данные из models.js
import {UserModel, UserType, UserInput} from './models';
// создаем поле для получения одного пользователя
const User = {
type: UserType, // тип для получения данных пользователя
args: {
// аргумент для поиска пользователь
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
// метод, в котором формируется запрос и возвращаются данные
resolve (root, params, options) {
return UserModel
.findById(params.id)
.exec(); // возвращаем JSON
}
};
const Users = {
type: new GraphQLList(UserType),
args: {},
resolve (root, params, options) {
return UserModel
.find()
.exec();
}
};
export default {
User: User,
Users: Users,
}
Mutations практически аналогичны queries. Queries выполняются асинхронно, а mutations последовательно, один за одним. Добавьте код в mutations.js .
import {
GraphQLNonNull,
GraphQLBoolean,
} from 'graphql';
import {UserModel, UserType, UserInput} from './models';
const UserCreate = {
description: "Create new user",
type: GraphQLBoolean,
args: {
data: {
name: "data",
type: new GraphQLNonNull(UserInput)
}
},
async resolve (root, params, options) {
const userModel = new UserModel(params.data);
const newUser = await userModel.save();
if (!newUser) {
throw new Error('Error adding new user');
}
return true;
}
};
export default {
UserCreate: UserCreate,
}
Я вас поздравляю! Вы создали свой GraphQL сервер с нуля. Осталось его протестировать.
npm run dev
Если в консоле ошибки, то обратитесь к рабочему репозиторию Github. При запущеном, рабочем сервере, перейдите по ссылке в браузере:
http://localhost:3000/graphiql
В открытом окне, напишите свои первые мутации:
mutation firstMutation{
UserCreate(data: {firstName: "John", lastName: "Dohe"})
}
mutation secondMutation{
UserCreate(data: {firstName: "Jane", lastName: "Dohe"})
}
При успешном результате вы получите:
{
"data": {
"UserCreate": true
}
}
Последним шагом, выведем всех пользователей из MongoDB:
{
Users{
firstName
lastName
}
}
В ответ мы получим:
{
"data": {
"Users": [
{
"firstName": "John",
"lastName": "Dohe"
},
{
"firstName": "Jane",
"lastName": "Dohe"
}
]
}
}
На этом мы закончили. Возможно, к этой статье вы пришли с базовыми знаниями или нашли для себя что-то новое. Так, начиная с простых вещей о node.js, создания сервера на Koa и подключением к MongoDB, полулось собрать полностью рабочий GraphQL сервер.
Если у вас имеются вопросы или пожелания, я с удовольствием отвечу в комментариях.
Спасибо всем за внимание.