GraphQL сервер с Koa2 и MongoDB

в 15:15, , рубрики: graphql, graphql-server, javascript, koa, node.js, метки: ,

Привет Мир! Сервер — это сердце любого проекта. Сегодня я расскажу, как заставить его биться, чтобы вы смогли использовать в разных целях. Начиная от SPA, мобильный платформ Android + iOS и ограничиваясь лишь вашей фантазией.

GraphQL сервер с Koa2 и MongoDB - 1

Тот кто знаком с 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

Вам будет доступен графический инструмент для тестирования запросов. Получилось? Отлично.

Пришло время сделать небольшой перерыв. Налейте себе кофе или чай, в зависимости от того, что вам больше нравится. Позже мы продолжим говорить о самом интересном.

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 .

Вся схема взаимосвязи будет выглядеть следующим образом:

GraphQL сервер с Koa2 и MongoDB - 2

Прежде чем углубляться, давайте разберемся. Помните, у 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 сервер.

Если у вас имеются вопросы или пожелания, я с удовольствием отвечу в комментариях.

Спасибо всем за внимание.

Автор: nikitamarcius

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js