Вступление
Heroku – это хостинг, поддерживающий приложения Node.js. Он прост в использовании, а его функциональность можно расширить с помощью дополнительных модулей. Существуют дополнения для различных вещей, включая обмен сообщениями через очереди, протоколирование, метрики и, конечно, хранилища данных. Дополнения для хранилищ данных поддерживают такие популярные базы данных, как PostgreSQL, Redis и DynamoDB.
В этом руководстве мы добавим базу данных PostgreSQL в приложение Node, которое сокращает URL-адреса. Затем мы развернем приложение на Heroku и настроим дополнение PostgreSQL.
PostgreSQL
Если у вас его еще нет, вам нужно установить Postgres на вашу машину. Существует несколько различных способов его установки, в зависимости от вашей ОС. Посетите страницу загрузки PostgreSQL для получения дополнительной информации.
Установив PostgreSQL, мы можем создать базу данных для приложения URL shortener:
$ psql psql (11.6) Type "help" for help. tomkadwill=#
А затем использовать SQL команду CREATE DATABASE:
tomkadwill=# CREATE DATABASE urlshortener_development; CREATE DATABASE tomkadwill=# \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges --------------------------+------------+----------+-------------+-------------+----------------------- urlshortener_development | tomkadwill | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
Здесь мы создаем базу данных под названием urlshortener_development, а затем используем \l для печати списка всех баз данных PostgreSQL в системе.
Наша новая база данных urlshortener_development находится там, поэтому мы знаем, что она была создана успешно. Также обратите внимание на владельца базы данных, потому что он нам понадобится позже (ваш владелец будет отличаться от моего).
Интеграция Postgres в приложение Node
Приложение Node, над которым мы будем работать, довольно простое. Если вы хотите создать его с нуля, вы можете следовать нашему руководству “Развертывание приложения Node.js на Heroku” или загрузить его с GitHub.
Вся логика приложения Express находится внутри app.js:
const express = require('express'); const app = express(); const path = require('path'); const port = process.env.PORT || 3000; const urlShortener = require('node-url-shortener'); const bodyParser = require('body-parser') app.use(bodyParser.urlencoded({extended: true})); app.use(express.urlencoded()); app.get('/', function(req, res) { res.sendFile(path.join(__dirname + '/index.html')); }); app.post('/url', function(req, res) { const url = req.body.url urlShortener.short(url, function(err, shortUrl){ res.send(shortUrl); }); }); app.listen(port, () => console.log(`url-shortener listening on port ${port}!`));
Вы можете запустить приложение с помощью npm start. После запуска перейдите на localhost:3000, и вы увидите домашнюю страницу:
План состоит в том, чтобы обновить app.js таким образом, чтобы он сохранял каждый URL и сокращенный URL в таблице DB, а затем отображал последние 5 результатов на пользовательском интерфейсе.
Первое, что нам нужно сделать, это установить библиотеку ORM (Object Relation Mapper). Взаимодействовать напрямую с PostgreSQL сложно, поскольку нам придется писать собственные необработанные SQL-запросы.
ORM позволяет нам взаимодействовать с базой данных через более простые вызовы API. Обратите внимание, что у использования ORM есть некоторые недостатки, но я не буду рассматривать их в этом учебнике.
Существует несколько различных библиотек ORM для Node, в данном случае мы будем использовать Sequelize:
$ npm install --save sequelize $ npm install --save pg pg-hstore
Первая команда устанавливает Sequelize, а вторая – драйвер PostgreSQL для Node. Sequelize поддерживает несколько баз данных, поэтому нам нужно указать, какую из них использовать, и предоставить драйвер Node.
Миграции
Когда Sequelize установлен и настроен, мы можем подумать о структуре базы данных. Нам нужно что-то простое, одна таблица с 3 столбцами: уникальный ID, оригинальный URL и сокращенный URL.
Мы можем создать новую таблицу базы данных вручную, но это сделает развертывание болезненным. Нам пришлось бы запоминать наши запросы и запускать их в каждой среде.
Лучший способ обработки изменений базы данных – миграции, при которых изменения базы данных кодируются внутри приложения. К счастью, Sequelize поддерживает миграции из коробки. Давайте напишем миграцию для создания таблицы для URL-адресов.
Сначала мы установим Sequelize CLI, который позволяет нам запускать миграции:
$ npm install --save sequelize-cli
Далее мы инициализируем Sequelize:
$ npx sequelize-cli init
В результате будет создан файл config/config.json и каталоги models, migrations и seeders.
После этого нам нужно изменить файл config.json так, чтобы он мог подключиться к нашей базе данных PostgreSQL:
{ "development": { "username": "tomkadwill", "password": "password", "database": "urlshortener_development", "host": "localhost", "dialect": "postgres", "operatorsAliases": false } }
Как только файл будет сделан, давайте генерируем миграцию, Когда файл готов, давайте сгенерируем миграцию с помощью Sequelize CLI. Здесь мы определим наши поля с помощью флага attributes. Мы не будем включать поле id, поскольку оно добавляется автоматически:
$ npx sequelize-cli model:generate --name Url --attributes url:string,shortUrl:string
Это создаст файл миграции, который будет выглядеть примерно так:
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Urls', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, url: { type: Sequelize.STRING }, shortUrl: { type: Sequelize.STRING }, createdAt: { allowNull: false, type: Sequelize.DATE }, updatedAt: { allowNull: false, type: Sequelize.DATE } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Urls'); } };
Миграции содержат функции вверх и вниз. Вверх используется для продвижения базы данных вперед, а вниз – для отката назад.
В данном случае функция up создает таблицу Urls, содержащую 5 полей. В ней есть поля url и shortUrl, а также id, createdAt и updatedAt, которые добавляются по умолчанию.
При миграции вниз таблица Urls будет просто удалена.
Наконец, давайте запустим миграцию:
$ npx sequelize-cli db:migrate
После этого мы можем напрямую запросить базу данных, чтобы проверить, что все сработало:
$ psql -p 5432 "urlshortener_development" psql (11.6) Type "help" for help. urlshortener_development=# \dt List of relations Schema | Name | Type | Owner --------+---------------+-------+------------ public | SequelizeMeta | table | tomkadwill public | Urls | table | tomkadwill (2 rows) urlshortener_development=# \d "Urls" Table "public.Urls" Column | Type | Collation | Nullable | Default -----------+--------------------------+-----------+----------+------------------------------------ id | integer | | not null | nextval('"Urls_id_seq"'::regclass) url | character varying(255) | | | shortUrl | character varying(255) | | | createdAt | timestamp with time zone | | not null | updatedAt | timestamp with time zone | | not null | Indexes: "Urls_pkey" PRIMARY KEY, btree (id)
Как вы можете видеть, есть две таблицы базы данных: SequelizeMeta и Urls. И если мы проверим Urls, ожидаемые поля находятся там.
Сохранение URL-адресов
Теперь, когда у нас настроен PostgreSQL и мы создали таблицу базы данных с правильной структурой, следующим шагом будет обновление нашего приложения, чтобы оно сохраняло URL в базе данных. Вспомните, что npx sequelize-cli model:generate создал файл модели, который мы будем использовать для сохранения URL в базе данных.
Сначала нам нужно импортировать модели в app.js, требуя models/index.js:
const db = require('./models/index.js');
Файл models/index.js был сгенерирован Sequelize, и его цель – включить все файлы моделей.
Далее нам нужно обновить маршрут post так, чтобы он создавал запись в базе данных. Мы можем использовать функцию findOrCreate(), чтобы каждый URL имел только одну уникальную запись в базе данных:
app.post('/url', function(req, res) { const url = req.body.url urlShortener.short(url, function(err, shortUrl) { db.Url.findOrCreate({where: {url: url, shortUrl: shortUrl}}) .then(([urlObj, created]) => { res.send(shortUrl) }); }); });
Когда вызывается db.Url.findOrCreate(), она пытается найти запись, которая соответствует предоставленному пользователем url и сгенерированному shortUrl. Если такая запись найдена, Sequelize ничего не делает, в противном случае создается новая запись.
Отображение URL-адресов в пользовательском интерфейсе
Для удобства пользователей давайте обновим приложение, чтобы оно отображало последние 5 сохраненных URL.
Для этого мы добавим шаблонизатор, чтобы Express мог отображать URL динамически. Существует множество шаблонизаторов, но в данном случае мы будем использовать Express Handlebars:
$ npm install --save express-handlebars
После установки пакета мы можем добавить его в app.js:
const exphbs = require('express-handlebars'); app.engine('handlebars', exphbs()); app.set('view engine', 'handlebars');
Далее нам нужно изменить структуру каталогов приложения. express-handlebars предполагает, что представления находятся в каталоге views. Он также предполагает, что у нас есть файл views/layouts/main.handlebars:
. ├── app.js └── views ├── index.handlebars └── layouts └── main.handlebars
Итак, давайте переместим и переименуем файл index.html:
$ mv index.html views/index.handlebars
И, наконец, давайте создадим файл макета views/layouts/main.handlebars:
<html> <head> <title>Url Shortener</title> </head> <body> {{{body}}} </body> </html>
Существует определенный порядок загрузки файлов шаблонов: express-handlebars отобразит views/layouts/main.handlebars, который затем отобразит views/index.handlebars внутри тега {{body}}.
Теперь, когда у нас есть правильная структура каталогов, давайте добавим немного HTML-кода в index.handlebars для динамического отображения URL-адресов:
<ul> {{#each urlObjs}} <li>{{this.url}} -- <b>{{this.shortUrl}}</b></li> {{/each}} </ul>
Здесь мы используем помощник Handlebars’s each для итерации каждого объекта URL и отображения оригинального URL и короткого URL.
Последнее, что нам нужно сделать, это обновить маршрут GET в app.js, чтобы передать URL в представление:
app.get('/', function(req, res) { db.Url.findAll({order: [['createdAt', 'DESC']], limit: 5}) .then(urlObjs => { res.render('index', { urlObjs: urlObjs }); }); });
Давайте рассмотрим, что здесь происходит. Когда запрашивается /, Sequelize запрашивает таблицу Urls. Запрос упорядочен по createdAt и ограничен 5, что гарантирует, что будут возвращены только 5 последних результатов.
Результат запроса передается в res.render как локальная переменная urlObjs, которая будет использоваться шаблоном.
Отрисованная страница выглядит следующим образом:
Если у вас возникли проблемы, вы можете загрузить готовый код с GitHub.
Развертывание приложения на Heroku
Теперь, когда у нас есть приложение, работающее локально, в этом разделе мы рассмотрим, как запустить его на Heroku и как подключить к нему базу данных после запуска.
Сначала давайте развернем все наши изменения на Heroku:
$ git push heroku master
Подробное руководство по развертыванию приложения Node.js на Heroku см. в разделе Развертывание приложения Node.js на Heroku.
Подключение базы данных
Добавить базу данных несложно, и все, что для этого требуется, – одна командная строка:
$ heroku addons:create heroku-postgresql:hobby-dev
Эта команда создает дополнение PostgreSQL для Heroku и устанавливает переменную окружения под названием DATABASE_URL – нам остается только указать Sequelize использовать ее, обновив файл конфигурации:
{ "development": { "username": "tomkadwill", "password": "password", "database": "urlshortener_development", "host": "localhost", "dialect": "postgres", "operatorsAliases": false }, "production": { "username": "tomkadwill", "password": "password", "database": "urlshortener_production", "host": "localhost", "dialect": "postgres", "operatorsAliases": false, "use_env_variable": "DATABASE_URL" } }
Здесь мы добавили новый раздел конфигурации для production, он такой же, как и для development, за исключением имени базы данных и поля use_env_variable.
Sequelize использует use_env_variable для получения имени переменной окружения, которую нужно использовать для подключения к базе данных.
У Heroku по умолчанию NODE_ENV имеет значение “production”, что означает, что он будет искать производственные конфигурации. Давайте зафиксируем и перенесем на Heroku.
Далее нам нужно запустить миграции на Heroku с помощью sequelize db:migrate:
$ heroku run bash Running bash on ⬢ nameful-wolf-12818... up, run.5074 (Free) ~ $ sequelize db:migrate
Если бы мы не создали миграцию раньше, мы бы сейчас запускали скрипты вручную.
На данном этапе приложение должно работать в браузере.
Управление базой данных Heroku
Вероятно, в какой-то момент вам понадобится запросить или изменить производственную базу данных. Например, вам может понадобиться запросить некоторые данные, чтобы помочь диагностировать ошибку, или изменить данные пользователя. Давайте рассмотрим, как это сделать.
Выполнение задач миграции Sequelize
Первое, что вам нужно знать, – как запускать миграции Sequelize на Heroku. Вот как просмотреть список доступных команд:
$ heroku run bash Running bash on ⬢ nameful-wolf-12818... up, run.1435 (Free) ~ $ sequelize
Это некоторые из наиболее полезных:
- sequelize db:migrate:undo: Откат одной миграции
- sequelize db:migrate:undo:all: Откат всех миграций. Эффективный возврат к чистой базе данных
- sequelize db:migrate:status: Проверить, на какой миграции находится ваше приложение
Использование моделей Sequelize в консоли
В дополнение к задачам CLI, мы можем использовать модели Sequelize непосредственно в консоли Node:
$ heroku run bash ~ $ node Welcome to Node.js v12.14.0. Type ".help" for more information. >
Здесь приведены примеры кода, которые можно выполнить в консоли Node. Вы можете заметить, что они имеют сходство с Node REPL.
Запрос отдельного URL по идентификатору:
db.Url.findByPk(1).then(url => { console.log( url.get({plain: true}) ); });
Запрос по всем URL-адресам:
db.Url.findAll().then(urls => { urls.map(url => { console.log( url.get({plain: true}) ); }); });
Вставка записи URL-адреса:
db.Url.create({url: 'https://stackabuse.com/deploying-a-node-js-app-to-heroku', shortUrl: 'https://is.gd/56bEH3'});
Запрос URL по идентификатору и его обновление:
db.Url.findByPk(1).then(url => { url.shortUrl = 'example.com'; url.save(); });
Запрос URL по идентификатору и его удаление:
db.Url.findByPk(1).then(url => { url.destroy(); });
Заключение
Существует множество дополнений, которые можно использовать для расширения Heroku. Одним из таких дополнений является Heroku Postgres, который позволяет легко настроить базу данных для хранения данных приложения.
Мы расширили простое приложение Node и Express, чтобы оно хранило урлы в базе данных Postgres на Heroku.
Оригинал: “https://stackabuse.com/adding-a-postgresql-database-to-a-node-js-app-on-heroku/”