API на Nodejs, GraphQL, MongoDB, Hapi и Swagger

Работа сис. админа
2 мин. на чтение

Хотелось дописать какие-то превосходительные степени сравнения в шапку вроде «Мощный API» или «Функциональный API», но понял что это лакмусовая бумажка для трепа вокруг да около и просто написано – API.

Собственно, немного про разделение Frontend и backend и какие оно имеет преимущества:

  • Самое большое преимущество так это то, что такая модель делает вашу систему нечувствительной к типу клиента – веб браузер это или мобильное приложение и даже десктопное – вам все равно;
  • Разделение зон ответственности. Времена, когда приложения были монолитными к счастью давно прошли. Это означало, что если у вас очень сложное и запутанное приложение единственным вариантом развития было нанимать очень опытных, крутых и крайне дорогих специалистов.

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

Так вот, разделение вашего приложения на frontend/backend, а то и вовсе на микросервисы может значительно уменьшить избыточную сложность проекта. А разделение ответственности, когда есть команда бекендщиков и фронтендеров дает возможность развивать проект силами не только сеньеров, но менее дорогими опытными специалистами.

Все, что было замечено выше делает рост количества/качества вашей команды более простым. Вы можете находить более узких спецов – backend developer, frontend developer, devops и так далее. Таких специалистов на рынке больше чем скажем full-stack да и зарплаты на них значительно ниже.

Планируем и начинаем

Сегодня мы построим функциональный и достаточно гибкий API с GraphQL на Nodejs, который будет документироваться Swagger при помощи MongoDB.

Основным бекбоном нашего API будет Hapi.js, но мы рассмотрим все технологии в деталях по ходу настройки проекта.

Вишенкой на торте станет интеграция с клиентами на React, Vue или Angular.

Предварительные требования

Скажем так, мы не можем объять необъятное, поэтому все-таки кое-какие тулы у вас уже дожны быть. Вот, что нужно для начала (псссс, на самом деле у среднестатистического разработчика все это есть):

  • установленная Nodejs;
  • базовый опыт разработчки на JavaScript;
  • любой терминал, мне нравится на Mac iTerm;
  • тестовый редактор, моем случае это Sublime;
  • MongoDB.

Приступим?

Прежде всего создадим начальную структуру папок и создадим пустой проект.

mkdir modern-api && cd modern-api && npm init -y

Далее настроим наш Hapi сервер. Для этого установим пакет и все необходимые зависимости:

yarn add hapi nodemon

Наверное, вам интересно почему hapi.js, а не скажем Express? Hapi отлично заточен на то, чтобы разработчики концентрировались на написании кода, а не обустройстве серверного окружения. Hapi позволит нам построить API в достаточно быстрой манере (пока поверьте на слова, а там сами убедитесь).

Про вторую зависимость – nodemon даже не удобно говорить, т.к. это незаменимая вещь при разработке. Это тула, которая перезапускает ноду каждый раз, когда вы меняете исходный код вашего приложения (тем самым применяя его).

Теперь, когда у нас готов базовый скелет проекта, давайте немного покодим. Откроем текстовый редактор, я сделаю это в Sublime и покодим index.js

const hapi = require('hapi');

const server = hapi.server({
	port: 4000,
	host: 'localhost'
});

const init = async () => {
	await server.start();
	console.log(`Server run at: ${server.info.uri}`);
};

init();
  • Мы подключили модуль Hapi;
  • Мы создали константу server, которая создает новый инстанс нашего Hapi  сервера, который в качестве аргументов получил порт и имя хоста;
  • Ну и наконец мы создали асинхронную конструкцию под названием init. Внутри конструкции init «живет» другой асинхронный метод, который запускает сервер. Ну и в конце мы вызвали функцию init();

Собственно, если мы сейчас запустим наш сервер через node index.js, то результат в браузере может немного удивить:

В принципе ничего странного тут нет, т.к. Hapi ожидает от нас route и handler, которых пока нет в коде.

Немного прервемся на полезную доработку и решим вопрос с nodemon. Для этого откроем наш package.json и отредактируем секцию scripts таким образом:

"scripts": {
"start": "nodemon index.js"
},

Теперь можно запускать проект через yarn start.

Как видите yarn при старте запустил nodemon и теперь все правки кода будут применяться автоматически (перезапускать Нода).

Роутинг

Роутинг в Hapi что-то на уровне интуиции – прост и понятен. К примеру, вы обратились на /, логично что это корень домена. Для конфигурации у вас доступны 3 компонента:

  • Путь – path
  • Какой HTTP метод (POST, GET, PUT…) – method
  • Что произойдет при обращении на path – handler.

Добавим все это в наш код:

const hapi = require('hapi');

const server = hapi.server({
	port: 4000,
	host: 'localhost'
});

const init = async () => {
	server.route({
		method: 'GET',
		path: '/',
		handler: function (request, reply) {
			return '<h1>Мой супер API</h1>';
		}
	});
	await server.start();
	console.log(`Server run at: ${server.info.uri}`);
};

init();

Сохраняем (у нас сразу сработал nodemon), открываем наш localhost:4000 и видим наш корневой хендлер:

Не плохо (для школьника), пока добавить что-то повеселее.

Say hello to MongoDB

Обновлено 21.12.2021: Сервис Mlab прекратил свое существование. Актуальная информация о том, как установить и использовать MongoDB можно в этой статье.

Сейчас давайте добавим базу данных, в нашем случае это будет mongodb с mongoose.

yarn add mongoose

Ну и вас код index.js добавим модуль:

const mongoose = require('mongoose');

А вот тут небольшое отличие. Вместо того, чтобы запускать Монгу на своей локальной машинке, можно использовать облачное решение с бесплатным тарифным планом для прототипирования. В нашем случае это будет mlab. Простенькая регистрация у нас уже есть база до 500 Мб., что более чем достаточно для наших учебных целей. Да, только не забудьте подтвердить свой email, а то не получится создавать базы данных.

Давайте создадим новую базу:

Выбираем локацию поближе, в моем случае это Западная Европа (Ireland). После создания базы, не забывайте создать нового юзера, а то конектится к серверу будет собственно нечем.

Подключаем mongoose к mlab

Возвращаемся к нашему index.js и допишем секцию подключения к серверу баз данных, у вас получится строка вида:

mongoose.connect('mongodb://<userName>:<userPass>@<serverhost>.mlab.com:<port>/<database-name>');

Чтобы убедиться, что подключение к базе прошло успешно, давайте что-то выведем в консоль, сделаем это следующим кодом:

mongoose.connection.once('open', () => {
	console.log('Connected to DB server successfuly');
});

Ну и как результат, все у нас работает:

Это уже что-то поинтереснее, чем Hello World, так что можно открыть себе пива перед следующей частью, в которой мы будем создавать Модели в MongoDB. 🍻

Создание Моделей в БД

С помощью MongoDB начнем создавать Модели, ну или другими словами приступим к моделированию данных. Выберем какую-то простую концепцию, которую вы будете в состоянии понять. По сути мы просто объявим схему для коллекций. Если слово «коллекции» вас пугают, думайте о них как о таблицах в реляционных БД (SQL).

В структуре нашего проекта создадим новую папку – models. А внутри создадим новый файлик Painting.js. Это будет наша Модель о рисовании, которая будет хранить всю информацию о рисовании, вот такой получится код:

const mongoose = require('mongoose');

const Schema = mongoose.Schema;

/*
	Обратите внимание, что у нас нет никаких ID. Это потому, что MongoDB автоматически
	присвоит индексы всем схемам

*/

const PaintingSchema = new Schema({
	name: String,
	url: String,
	techniques: [String]
});

module.exports = mongoose.model('Painting', PaintingSchema);

Пробежимся быстренько по тому, что мы тут навояли:

  • Мы подключили модуль mongoose;
  • Мы задекларировали нашу константу PaintingSchema через вызов встроенного в mongoose конструктора схем с добавлением опций. Обратите внимание на типы наших полей. Наприем name у нас это строка, а вот techniquesмассив строк;
  • Ну и напоследок мы экспортировали модель, назвав ее Painting.

Давайте получим данные о Рисовании из базы

Для начала импортируем модель Painting в index.js

const Painting = require('./models/Painting');

Ну и нам наверное захочется иметь свои API роуты для получения данных и внесения их в БД.

Что-то типа /api/v1/paintings/api/v1/paintings/{id}

Добавим в наш Hapi сервер новые роуты. Логично что метод GET у нас будет на получение данных, а POST на добавление в БД.

Обратите внимание, что теперь роуты у нас это массив, следите за разметкой 😀

const init = async () => {
	server.route([
		{
			method: 'GET',
			path: '/',
			handler: function (request, reply) {
			return '<h1>Мой супер API</h1>';
			}
		},
		{
			method: 'GET',
			path: '/api/v1/paintings',
			handler: (req, reply) => {
				return Painting.find();
			}
		},
		{
			method: 'POST',
			path: '/api/v1/paintings',
			handler: (req, reply) => {
				const { name, url, techniques } = req.palyload;
				const painting = new Painting({
					name,
					url,
					techniques
				});
			return painting.save();

			}
		}
		]);
	await server.start();
	console.log(`Server run at: ${server.info.uri}`);
};

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

  • Итак, мы создали GET запрос для роута /api/v1/paintings, по этому роуту мы вызываем handler, в котором обращаемся к в mongoose schema. Внутри нее есть встроенный метод find(), который умеет делать выборку из коллекции, но в нашем случае вернет все, т.к. мы не задали каких-то условий поиска;
  • Мы так же создали POST запрос для аналогичного роута. Почему для того же? Ну есть некие условные [itg-tooltip href=»http://tooltip» tooltip-content=»<p>На самом деле REST не имеет стандартизации в полном понимании этого слова, это скорее добровольное соглашение между разработчиками. У REST нет организации, как скажем W3 для HTML.</p>»]«стандарты»[/itg-tooltip] для REST API.  Давайте разберем наш хендлер. Как вы помните для нашей схемы Painting мы создали три поля — name, url и techniques. С нашего POST запроса мы принимаем три параметра (скажите привет, Postman) и отправляем их в нашу схему. Как только мы передали аргументы, вызываем метод save(), который создает новую запись в БД.

Если просто сейчас обратиться в браузере по адресу http://localhost:4000/api/v1/paintings, то увидим следующее:

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

Пау-пау-пау, у нас все работает, судя по ответу. Можем для разнообразия попробовать снова GET через браузер:

В следующей части статьи добавим все оставшиеся компоненты – GraphQL и Swagger.

Оригинал статьи на English.

Ihor Chyshkala

Пишу статьи про ИТ в свободное от работы время.

Оцените автора
Авторский блог Игоря Чишкалы
Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

  1. Вася

    Лучше бы ты книги читал в свободное от работы время! :—)

    Ответить
    1. Ihor Chyshkala автор

      Приветствую!
      Хотите посоветовать что-то конкретное?

      Ответить
  2. Виктор

    Было бы здорово, если бы вы оставили ссылочку на оригинальную серию статей

    Ответить
    1. Ihor Chyshkala автор

      Готово! Спасибо, что обратили внимание :)

      Ответить