- PVSM.RU - https://www.pvsm.ru -

Хостинг Node.js https сервера с авто-обновляемым SSL в облаке и как я настроил цикл разработки (+ git, react)

Предисловие

Начну с того, что однажды мне захотелось создать приложение. Желание такое возникло из-за того, что я люблю читать, а нормальных книжных агрегаторов на просторах русского интернета просто нет. Собственно из боли поиска чего бы почитать и попыток вспомнить как называлась та книжка, которую я недавно читал и на какой же главе я остановился, родилось желание сделать веб-приложение, в котором всё это было бы возможно и удобно. Стоит отметить, что никакого опыта разработки, программирования и т.п. у меня не было, моя работа вообще с этим не связана. Тем не менее желание перебороло лень и переросло в конкретные действия, своеобразное хобби.

Не буду рассказывать как я изучал javascript, node.js, react, html, css и т.п., перейдём к тому, к чему я пришел на данный момент, чем хотел бы с вами поделится и, конечно, послушать конструктивную критику специалистов.

Как и многие я тренировался на собственном ПК на localhost:3000, создавал front/back-end'ы, верстал, работал с api и т.д., но меня всегда тревожила мысль а том, как же всё это потом перенести на хостинг [1]? Будет ли оно работать? Нужно ли будет переписывать из-за этого код? И самое главное, нельзя ли всё настроить так, чтобы я мог работать над приложением с любого ПК и легко переносить всё на хостинг [1] на production? Об этом я и расскажу.

Выбор хостинга

На своё хобби я готов был тратить 10$ в месяц, поэтому выбирал тот хостинг [1], с которым планировал и остаться в будущем. Как я и говорил, до этого у меня был 0 опыт, в том числе и с хостингом [1] сайтов. Я попробовал и отказался от следующих:

Jelastic: красивый и удобный интерфейс, вроде всё интуитивно, масштабируемо и понятно. Тем не менее столкнулся с трудностями при настройке (nginx почему-то из vps [1] не хотел работать, только их отдельным модулем) и подключении SSL(и автоматическом обновлении) к русскоязычном домену стандартными средствами (обещали баг пофиксить, но я не хочу ждать)

Облачный хостинг [1] REG.RU: тут же у меня и домен, поэтому решение казалось логичным, однако у них не было отдельно настроенного PostgreSQL, а так как с администрированием базы мне связываться не хотелось, начал искать дальше.

AWS и Google облака: попробовал, всё вроде хорошо, но вспомнил про наши «замечательные» законы и требование размешать данные пользователей на сервера в РФ. У этих ребят, к сожалению, серверов в РФ не оказалось. Не юрист, но от греха решил поискать облака с серверами в РФ. Если же ваше приложение маловероятно будет иметь проблемы с законом, то хороший выбор.

Облака с серверами в РФ хоть и были, но хотелось всё же что-то, что избавит меня от необходимости погружаться в администрирование PostgreSQL. Порыв немного наткнулся на не так давно ставшие доступными Яндекс.Облака [2], попробовал, вроде всё просто и удобно, поэтому остановился пока на них. Стоит отметить, что хостинг [1] PostgreSQL у них сразу идёт с 1core и 4гб RAM, что по стоимости около 2к рублей в месяц, поэтому на время разработки и невысокой нагрузки я планирую запустить PostgreSQL на VPS [1] за ~300р, а с повышением нагрузки перенести базу и пусть Яндекс занимается администрированием и обновлением.

Настройка Яндекс.Облака

Virtual Private Cloud

1) Создаём каталог под свой сайт:

image

2) Создаём Virtual Private Cloud:

Главное что он даёт для меня на текущем этапе — это IP для доступа к созданному ресурсу из вне. С подсетями, зонами, изоляцией и отказоустойчивостью ознакомился поверхностно, при необходимости наверстаю.

3) Создаём подсеть и присваиваем ей внутренний IP (как я понял, это примерно как локальная сеть)

image

4) Переходим на вкладку IP и резервируем себе статический IP.

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

image

Compute Cloud

Тут у нас будут происходить вычисления :) То есть мы создадим виртуальную машину с Linux (я выбрал ubuntu 18.04), установим node.js приложения и postgreSQL.

image

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

SSH

Проблемный момент, с которым я столкнулся на этом этапе, это SSH:

image

Что это и зачем понятия не имел, поэтому пошел изучать. Оказалось — это просто метод доступа, но не по паролю, а по сгенерированному SSH ключу. Чтобы собственно его сгенерировать, скачиваем и устанавливаем Putty [3] как нам советуют.

Запускаем C:Program FilesPuTTYputtygen.exe

image

Нажимаем кнопку Generate и водим мышкой, чтобы придать случайности сгенерированному ключа (как я понял). Далее копируем появившуюся строку начинающуюся с ssh-rsa куда-нибудь в текстовый файл и жмём Save private key, Save public key. Скопированный в текстовый файл ключ вставляем в поле SSH ключ открытой страницы Яндекса. Логин указываем root, иначе у вас не будет доступа при работе с графической файловой системой приложения по которому будете подключаться к облаку из дома/работы (возможно и есть способ, но я не разбирался).
Как заметил andreymal, лучше не использовать root, чтобы китайские боты не подобрали пароль к вашему облаку, но так как в яндекс.облаке доступ только по SSH, то жить вроде можно.

Приложение на хостинге [1] же стоит запускать исключительно не root пользователем, чтобы не позволить злоумышленникам выполнять вредный код через возможные уязвимости в вашем приложении.

Подключаемся к облаку с ПК и выбираем бесплатный SSH клиент

Стандартный Putty позволяет работать только командной строкой, а так как мне пользователю windows это непривычно, то я начал искать клиент с псевдо-проводником. Сначала я попробовал Mobaxterm, но он после какого-то времени простоя отключается, проводник вообще зависает, поэтому сейчас работаю с bitvise ssh [4] и пока проблем как у Mobaxterm не наблюдаю.

Настройка bitvise ssh

image

Тут в поле Server>Host указываем наш внешний IP облака. Порт 22. Нажимаем client key manager>import указываем в нём сгенерированный Putty ранее private ключ. Может ещё потребоваться ключевая фраза, выберите что-нибудь что не забудете. Закрываем это окошко и в поле authentication указываем username: root, method publick key, client key — выбираем импортированный ранее. Жмём login и если мы всё сделали правильно, то подключимся к облаку:

image

Устанавливаем Node.js

Тут я рекомендую пользоваться инструкциями digitalocean.com, они очень подробны и многие есть на русском. Обычно я так и гуглю «digitalocean ubuntu 18.04 node.js» или что вы там захотите установить или настроить.

Как установить Node.js можно почитать тут [5].

Если коротко, то заходим на nodesource [6](тут последние версии node.js можно установить), листаем сюда:

image

Копируем и по-очереди запускаем команды:

curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
sudo apt-get install -y nodejs

Проверяем как установилось командой

nodejs -v

Нам покажет версию node.js

npm -v

Нам покажет версию менеджера пакетов для node.js.

Далее идём в папку /opt/mysuperapp (my_super_app_name — эту папку вы должны создать). Каталог opt был выбран в качестве места расположения приложения после долгих гуглений «где уместно положить файлы node.js приложения в ubuntu».

Наконец-таки создаём файл server.js, который будет входной точкой приложения и вставляем туда код простого сервера на node.js:

const http = require('http');

const hostname = 'localhost';
const port = 80;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World!n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Порт 80 — это для http запросов, 443 — для https. Пока у нас сервер на http.

Сохраняем всё и запускаем командной:

node server.js

В консоли должно появиться строка 'Server running at localhost [7]:80/'

Теперь можно открыть браузер, ввести внешний IP (тот что в облаке яндекса у вашей ВМ ubuntu) и мы увидим «Hello World!»

Делаем всё удобно или цикл разработки с помощью git

Всё вроде работает, но мы же не будем работать всё время подключаясь к облаку. К тому же вдруг мы будем в будущем работать не одни.

Github

Github — это место, где будет лежать код нашего приложения. Если коротко, принцип работы для одного человека следующий:

  • На домашнем ПК разрабатываем наше приложение.
  • Сохраняем и в один клик выгружаем код на Github.
  • На хостинге [1] или на другом ПК скачиваем наше приложение с github, перезагружаем сервер (если это хостинг [1]) и новая версия нашего веб-приложения доступна во всемирной паутине.

Всё быстро, просто и удобно.

Собственно регистрируемся на Github и создаём private репозиторий для нашего приложения (он будет доступен только нам):

image

Копируем строку github.com/ReTWi/mysuperapp.git [8] для скачивания приложения.

image

  1. Возвращаемся в командную строку bitvise, останавливаем приложение нажав ctrl+c (если оно ещё работает).
  2. Переходим в каталог /opt и удаляем созданную нами папку с приложением

Git- это то, с помощью чего мы будем загружать наше приложение на github, а оттуда на хостинг [1] или другой ПК. Git — это отдельная тема для обсуждения, поэтому остановимся пока на этом.
Устанавливаем git на хостинге [9] командами:

sudo apt update
sudo apt install git

Проверяем всё ли хорошо установилось:

git --version

Должна показаться версия git.

Заполняем данные git (так и не понял зачем, но видимо могут быть какие-то занудные предупреждения).

git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"

Наконец-таки загружаем наше приложение на хостинг [1] командой:
(тут должна быть ссылка на ваше приложение )

git clone https://github.com/ReTWi/mysuperapp.git 

В каталоге /opt появится новый mysuperapp, где будут находится файлы нашего приложения загруженные из github.

Теперь пора повторить тоже самое для ПК и замкнуть цепочку ПК(разные) --> Github --> Хостинг [1]

Устанавливаем node.js на ПК [10].

Visual studio code

Для начала выберем редактор исходного кода, где будем работать. Я выбрал Visual studio code, так он прост, удобен, в нём много плагинов и можно настроить синхронизацию настроек если вы работаете с нескольких устройств. Собственно скачиваем, устанавливаем, запускаем, выбираем общую папку приложений, так как git clone создаст нам свою.

Плагины я использую следующие:

image

Устанавливаем git для ПК [11].
Открываем консоль в VScode с помощью ctrl+shift+` или terminal>new terminal

Отступление:

В консоли windows плохо с русскими символами и чтобы не было крякозяблов нужно открыть file>preferences>settings, ввести в поле terminal.integrated.shellArgs.windows, нажать

image

И добавить строку «terminal.integrated.shellArgs.windows»: ["-NoExit", "/c", «chcp 65001»],

image

Повторяем команду для загрузки файлов с github:

git clone https://github.com/ReTWi/mysuperapp.git 

В VScode нажимаем File>Open Folder и открываем папку нашего приложения.

Создаём файл server.js с тем же кодом простого сервера:

const http = require('http');

const hostname = 'localhost';
const port = 80;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World!n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Устанавливаем nodemon для автоматической перезагрузки сервера при изменениях в коде:

npm i nodemon -g

i — сокращение от install
g — глобальная установка (чтобы было доступно в консоли), а не только для нашего приложения.

Запускаем командой:

nodemon server.js

Открываем в браузере localhost [7]:80/ или просто localhost:80 и видим Hello World.

Теперь настало время проверить нашу цепочку ПК>Github>Hosting.

Скачиваем Github desktop для большего удобства, подключаем свой github аккаунт, затем нажимаем файл add local repository и указываем каталог нашего приложения.

В приложении мы видим изменения которые мы сделали по сравнению с загруженной с Github версией (мы добавили server.js):

image

Жмём «commit to master»>«push origin», таким образом загружая файлы с ПК на Github.

image

Заходим в браузере на наш github аккаунт и видим загруженный файл server.js:

image

Потренируемся ещё немного, в VScode заменим строку «res.end('Hello World !n');» на «res.end('OmNomNom');». Увидим, что сервер сам перезагрузился:

image

Проверим в браузере и увидим там сделанные нами изменения «OmNomNom».

Desktop github тоже покажет нам что мы поменяли строку:

image

Опять жмём «commit to master»>«push origin», чтобы отправить файлы на github.

Переключаемся на командную строку хостинга [1].

Останавливаем наше приложение, если оно ещё запущено (ctrl+c).

Скачиваем наше обновлённое приложение командами:

git config credential.helper store
git pull

Первая сохранит наши данные, чтобы постоянно не вводить логин и пароль. В дальнейшем нам достаточно будет git pull.

Установим pm2 — нечто похожее на nodemon, только для хостинга [1]:

npm i pm2 -g

Запустим приложение с помощью pm2, которые при следующем git pull на хостинге [1] сам перезагрузит наш сервер:

pm2 start server.js --watch

Откроем браузер по нашему внешнему IP облака и увидим наше «OmNomNom».

Таким образом мы замкнули цепочку работы с приложением и быстрым его развёртыванием на хостинге [1].

Создаём временные SSL сертификаты для HTTPS на localhost и хостинга

Заходим на сайт zerossl.com [12]

image

В поле domains, ip… вписываем сначала localhost, нажимаем generate и скачиваем 2 файла по кнопке:

image

Сохраняем их у нас в проекте в папке ssl/localhost.

Повторяем процедуру для внешнего IP Облака и сохраняем в ssl/myapp.

Запускаем более сложный https сервер node.js

Структура приложения:

image

  • client — тут будет лежать наш front-end. У меня react.
  • logs — сюда будут падать логи на хостинге [1]
  • node_modules — модули node.js
  • private — ваши приватные файлы, я там храню SSH доступы к облаку
  • server — ваш backend
  • ssl — ssl сертификаты для работы https на localhost и на хостинге [1]
  • .babelrc — настройки сборки react приложения webpack'om (позволяет использовать более современный JS при разработке frontend)
  • .gitignore — файлы, которые не будут перемещаться на github (git их как бы не видит)
  • client.js — точка входа для генерации react сборки
  • package.json — используемые вами node_modeles и различные снипеты команд.
  • package-lock.json — изменения в модулях (насколько я понял, по файлу будет проверяться одинаковые ли у вас установлены модули на хостинге [1] и на ПК).
  • pm2-watch.json — настройки запуска pm2 для хостинга [1]
  • README.md — обложка для github
  • server.js — точка запуска нашего backend сервера Node.js
  • webpack.config.js — конфигурация сборки react

.gitignore

Тут мы указываем те файлы/папки, которые мы не хотим выгружать на github. Они будут только на данном устройстве и git не будет отслеживать/показывать их изменения. Открываем и вставляем:

/node_modules/

/logs/*
# exception to the rule
!logs/.gitkeep 

/public/react_bundle.js
/public/isProd.js

Так как github не выгружает пустые папки, то можно внутрь что-нибудь положить, к примеру пустой файл .gitkeep. Сохраняем файл и закрываем.

package.json

Открываем и вставляем следующее (после // добавил комментарии)

{
  "name": "myapp", // название вашего приложения
  "version": "1.0.0",
  "description": "OmNomNom",
  "main": "server.js", 
  "scripts": {
    "server": "pm2 start pm2-watch.json", // командой npm run server можно запустить этот скрипт
    "client": "webpack -w --mode development", // командой npm client можно запустить этот скрипт. Собирает приложение реакт и отслеживает изменения в коде, автоматически обновляя сборку.
    "client-prod": "webpack --mode production", // собирает сжатый вариант для production
    "client-analyze": "webpack --mode production --analyze true" // собирает сжатый вариант для production и позволяет посмотреть размеры разных модулей приложения. Полезно для оптимизации
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/myapp/knigam.git" // ссылка на ваш репозиторий github
  },
  "author": "rtw",
  "license": "UNLICENSED", // запрет на любое использование (личное приложение)
  "bugs": {
    "url": https://github.com/myapp/knigam.git"
  },
  "homepage": "https://github.com/myapp/knigam.git#readme",
  "dependencies": {
    "@babel/core": "^7.2.2", // Современный js для frontend
    "@babel/plugin-transform-runtime": "^7.2.0",  // Современный js для frontend
    "@babel/preset-env": "^7.3.1",  // Современный js для frontend
    "@babel/preset-react": "^7.0.0",  // Современный js для frontend
    "ajv": "^6.8.1",  // Проверка типов переменных
    "babel-loader": "^8.0.5", // Современный js для frontend
    "babel-plugin-styled-components": "^1.10.0", // Работа со styled-components
    "css-loader": "^2.1.0", // Для сборки webpack'om css
    "fastify": "^2.0.0-rc.6", // аналог express, более живой и активно развивающийся
    "fastify-cookie": "^2.1.6", // Работа с куки
    "fastify-static": "^2.2.0", // Работа со статичными файлами
    "moment": "^2.24.0", // Работа со временем
    "pg": "^7.8.0", // Работа со временем
    "pino": "^5.11.1", // Работа с postgreSQL из node.js
    "pino-pretty": "^2.5.0", // Читаемые логи в консоли
    "react": "^16.8.1", // Frontend библиотека. Выбор был между ней и Vue.js, но второй больше фрэймворк. В реакте больше нужно делать руками, что полезно для обучения
    "react-dom": "^16.8.1", // React для работы с браузером
    "style-loader": "^0.23.1", // Для сборки webpack'om стилей, уже не помню
    "styled-components": "^4.1.3", // CSS in JS, очень удобно для динамических стилей и локализации стилей в компонентах
    "webpack": "^4.29.3", // Сборщик реакт приложения
    "webpack-bundle-analyzer": "^3.0.3", // Анализатор размеров модулей собранного реакт приложения
    "webpack-cli": "^3.2.3" // Консоль сборщик реакт приложения, не помню уже зачем
  }
}

Остановлюсь на двух основных фрэймворках/библиотеках выбранных для приложения:
Fastify был выбран в качестве альтернативы express.js, так как в первом уже есть экспериментальная поддержка htpp2, он активно развивается и мне кажется у него больше будущего, нежели у express.js, который стал очень неповоротлив и кое-как развивается. С другой стороны express.js уже долгое время в работе и по нему вам легче будет найти информацию.

React был выбран так как мне с ним было проще работать, понять и пробовать всё своими руками. Vue — показался уже чем-то со своими правилами, направлением. Хотя во Vue может и меньше придётся писать что-то своими руками, но так как приоритет сделан на обучении и для человека ранее не программировавшего react пошел как-то легче.

Сохраняем файл package.json и устанавливаем все модули указанные в dependencies командой:

npm i

У нас появится папка node_modules, в которой будут все модули для нашего приложения.

client — пока пустая папка
logs — внутри лежит файл .gitkeep, чтобы папка перекочевала на хостинг [1] и логи успешно туда падали. При разработке мы всё будем выводить в консоль.

public

Тут статические файлы нашего сайта будут лежать, изображения там, фавиконки и т.д.
Отдельно остановимся на двух файлах:
index.html:

<!DOCTYPE html>
<html>
  <head>
    <base href="/" />
    <meta charset="UTF-8" />
    <title>MyApp</title>
  </head>

  <body>
    <div id="cookies">Этот текст заменится содержимым react_bundle после его загрузки</div>
    <noscript
      >Система: онлайн подключение возможно только при наличии Javscript</noscript
    >
    <script src="react_bundle.js"></script>
  </body>
</html>

— тут у нас подгружается react-фронтэнд и рендерится в тег по его id.

isProd.js содержит единственную строку «module.exports = false»
Так как он находится в исключениях .gitignore, то не переносится. Соответственно на ПК мы устанавливаем его в false, а на хостинге [1] в true. Затем используем этот файл, чтобы понять в какой мы сейчас среде (разработка/продакшн). Мне показалось наиболее удобным, к тому же можно частично в коде поменять при разработке и проверить работу модулей в продакшне.

ssl — там лежат сохранённые ранее сертификаты в папках localhost и myapp

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": [">0.25%", "not ie 11", "not op_mini all"]
        }
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    "babel-plugin-styled-components",
    "@babel/plugin-transform-runtime"
  ]
}

Настройки для создания нашего react_bundle с поддержкой браузеров используемых более >0.25% пользователей.

client.js

import React from 'react'
import { render } from 'react-dom'
render(<div>Реакт!!</div>, document.getElementById('cookies'))

Рендерит наш фронтэнд в div с тегом cookies.

pm2-watch.json — позволяет на хостинге [1] командой «npm run server» запустить сервер с отслеживанием изменений в коде и автоматической перезагрузкой.

webpack.config.js

Сборщик реакт приложения:

const webpack = require('webpack'),
  path = require('path'),
  BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = (env, argv) => {
  let prod = argv.mode == 'production'

  let config = {
    entry: './client.js',
    output: {
      path: path.resolve('./public'),
      filename: 'react_bundle.js'
    },
    module: {
      rules: [
        {
          test: /.(js|jsx)$/,
          exclude: /node_modules/,
          loader: 'babel-loader'
        },
        {
          test: /.css$/,
          use: ['style-loader', 'css-loader']
        }
      ]
    },
    resolve: {
      alias: {
        client: path.resolve('./client/shared'),
        public: path.resolve('./public')
      }
    },
    plugins: [
      argv.analyze ? new BundleAnalyzerPlugin() : false,
      prod ? new webpack.optimize.AggressiveMergingPlugin() : false,
      new webpack.ContextReplacementPlugin(/moment[/\]locale$/, /ru/)
    ].filter(Boolean),
    optimization: {
      minimize: prod ? true : false
    },
    performance: {
      hints: false
    }
  }

  return config
}

Если коротко, то он открывает файл client.js и все что у него внутри, собирая react_bundle и помещая его в папку public, откуда потом через открытый index.html он будет загружен.

server.js

const isProd = require('./public/isProd'),
  fs = require('fs'),
  log = require('./server/logger'),
  path = require('path')

// Ошибки среды node.js, чтобы приложение никогда не падало
process.on('unhandledRejection', (reason, promise) => {
  log.error({ reason, promise }, 'серверный процесс unhandledRejection')
})
process.on('uncaughtException', err => {
  log.error({ err }, 'серверный процесс uncaughtException')
})

// Redirect server from http port 80 to https 443
const fastifyHttp = require('fastify')({
  logger: log,
  ignoreTrailingSlash: true
})
fastifyHttp.listen(80, '::', (err, address) => {
  if (err) {
    log.error({ err, address }, 'Ошибка при запуске HTTP сервера')
  } else {
    log.warn('Http cервер запущен')
  }
})
// Let's Encrypt challenge
fastifyHttp.get('/.well-known/acme-challenge/:file', (req, res) => {
  let stream = fs.createReadStream(
    path.join(__dirname + '/ssl/.well-known/acme-challenge/' + req.params.file)
  )
  res.type('text/html').send(stream)
})
fastifyHttp.get('/*', (req, res) => {
  res.redirect(301, 'https://' + req.headers.host + req.raw.url)
})
fastifyHttp.get('/', (req, res) => {
  res.redirect(301, 'https://' + req.headers.host + req.raw.url)
})

// Сервер
let fastifyOptions = {
  logger: log,
  ignoreTrailingSlash: true,
  http2: true
}
fastifyOptions.https = isProd
  ? {
      allowHTTP1: true,
      key: fs.readFileSync('./ssl/myapp/key.txt'),
      cert: fs.readFileSync('./ssl/myapp/crt.txt')
    }
  : {
      allowHTTP1: true,
      key: fs.readFileSync('./ssl/localhost/cert.key'),
      cert: fs.readFileSync('./ssl/localhost/cert.pem')
    }

const fastify = require('fastify')(fastifyOptions)
fastify.listen(443, '::', (err, address) => {
  if (err) {
    log.error({ err, address }, 'Ошибка при запуске сервера')
  } else {
    log.warn(
      `Сервер  запущен в ${
        isProd ? 'продакшен' : 'режиме разработки'
      }`
    )
  }
})

// Валидатор
fastify.setSchemaCompiler(schema => {
  return ajv.compile(schema)
})

// Ошибки fastify
fastify.setErrorHandler((err, req, res) => {
  log.error({ err, req }, 'fastify errorHandler')

  // Ошибка валидации данных запроса
  if (err.validation) {
    return res.send({
      error: 'Ошибка валидации данных запроса'
    })
  } else {
    return res.send({
      error:
        'Ошибка errorHandler'
    })
  }
})

// Статические файлы
fastify.register(require('fastify-static'), {
  root: path.join(__dirname, './public')
})

// Куки
fastify.register(require('fastify-cookie'), err => {
  if (err) log.error({ err }, 'fastify-cookie')
})

// Ответ на любой запрос исключая апи / несуществующая страница
// Тут мы на любой запрос отдаём index.html, по сути наш фронэнд
// Запросы фронтэнда у нас принимаются с префиксом api, то есть GET /api/userdata
fastify.setNotFoundHandler((req, res) => {
  res.sendFile('index.html')
})


// Routes
fastify.register(
  async openRoutes => {
    // Пути доступные всем
    openRoutes.register(require('./server/api/open'))

    openRoutes.register(async withSession => {
      // Пути доступные только после авторизации и проверки сессии
      // Проверяем прямо тут хуком, пример:
       ///withSession.addHook('preHandler', async (req, res) => {
        // if (!(await sessionManagerIsOk(req, res))) return
      // })

      withSession.register(require('./server/api/with_session'))
    })
  },
  { prefix: '/api' } // префикс всех путей
)

Папка server

Тут лежит на бэкэнд и все пути.
logger.js — в зависимости от среды isProd логирует или в консоль или в errors.log

'use strict'

const pino = require('pino'),
  isProd = require('../public/isProd')

let logOptions = isProd
  ? {
      level: 'warn', // уровень логирования
      timestamp: () => {
        return ',"time":"' + new Date() + '"'
      }
    }
  : {
      level: 'warn',
      prettifier: require('pino-pretty'),
      prettyPrint: {
        levelFirst: true,
        translateTime: true
      }
    }
let dest = isProd ? pino.destination('./logs/errors.log') : pino.destination(1)
let log = pino(logOptions, dest)

module.exports = log

server/api/
open.js — сюда добавляем наши пути.

'use strict'
module.exports = function(fastify, options, next) {

  fastify.route({
    method: 'GET',
    url: '/',
    handler: async (req, res) => {
      res.send('api / route')
    }
  })

  fastify.route({
    method: 'GET',
    url: '/hi',
    handler: async (req, res) => {
      res.send('api / route hi')
    }
  })
  next()
}

После настройки и проверки всего на Localhost, просто выгружаем всё на github, а оттуда git pull на хостинг [1]. Всё что на хостинге [1] нужно будет сделать, это установить модули node.js командой «npm i» и создать файл isProd.js

Автоматически обновляемый SSL

Когда вы купите себе домен и привяжете его к IP облака, пример инструкции для REG.RU [13], можно установить на сервере автоматически обновляемый бесплатный SSL для работы сайта по https.

Наш сервер работает без nginx. Возможно он понадобится нам в будущем как балансировщик нагрузки или более быстрый HTTP-сервер для раздачи статических файлов, но пока необходимости я в нём не вижу. Балансировка нагрузки пока нам не требуется, а касательно скорости раздачи статики сравнений я не нашел.

Перед установкой в папке ssl создадим папку .well-known, а в ней acme-challenge. Получится /opt/myapp/ssl/.well-known/acme-challenge

Для установке на сервере с node.js без nginx автоматически обновляемого SSL переходим по ссылке [14]. По очереди выполняем команды в консоли хостинга [1]:

sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot 
sudo certbot certonly

Выбираем второй способ проверки, который разместит в папке /opt/myapp/ssl/.well-known/acme-challenge определённый файл, а после подтверждения владельца сервера его удалит.

Указываем по запросу наш домен, к примеру: «example.com» и путь к ssl папке нашего приложения (сервер настроен так, что отдаст созданный ботом файл) "/opt/myapp/ssl".

Бот сам настроит cron-задачу для обновления сертификата до срока его истечения в течение 90-дней.

Не думал, что займёт столько времени всё написать, к 4 часам ночи уже мог что-то упустить :/

Интересно мнение хабравчан и специалистов, кто осилил это полотно или прочитал какие-то отдельные моменты. Как у вас устроен цикл разработки? Есть ли какие-то моменты в которых я ошибаюсь или поступаю не лучшим образом?

Автор: Максим

Источник [15]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/node-js/308575

Ссылки в тексте:

[1] хостинг: https://www.reg.ru/?rlink=reflink-717

[2] Яндекс.Облака: https://cloud.yandex.ru/

[3] Putty: https://www.putty.org/

[4] bitvise ssh: https://www.bitvise.com/download-area

[5] тут: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04

[6] nodesource : https://github.com/nodesource/distributions

[7] localhost: http://localhost

[8] github.com/ReTWi/mysuperapp.git: https://github.com/ReTWi/mysuperapp.git

[9] git на хостинге: https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-18-04-quickstart

[10] node.js на ПК: https://nodejs.org/en/

[11] Устанавливаем git для ПК: https://git-scm.com/download

[12] zerossl.com: https://zerossl.com/free-ssl/#self

[13] инструкции для REG.RU: https://www.reg.ru/support/domains/dns_servery_i_nastroika_zony/Nastroika-zony/kak-dobavit-zapis-a-poddomen

[14] по ссылке: https://certbot.eff.org/lets-encrypt/ubuntubionic-other

[15] Источник: https://habr.com/ru/post/439952/?utm_source=habrahabr&utm_medium=rss&utm_campaign=439952