Интеграция MongoDB в приложение Node.js: сбор и рендеринг данных с помощью EJS и Express

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

Данная серия мануалов поможет интегрировать MongoDB в существующее приложение Node. Базы данных NoSQL (к которым относится MongoDB) подходят для управления данными, которым нужна высокая масштабируемость и гибкость. Также MongoDB хорошо интегрируется с Node, поскольку она разработана для асинхронной работы с объектами JSON.

Читайте также: Основы работы с JSON

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

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

Примечание: Прежде чем приступить к работе, выполните первую часть этой серии – мануал Интеграция MongoDB в приложение Node.js: Создание модели и контроллера.

1: Использование EJS и Express

Чтобы приложение могло работать с пользовательскими данными, мы сделаем две вещи: во-первых, мы включим встроенную функцию промежуточного программного обеспечения Express, urlencoded(), которая позволит приложению анализировать введенные данные пользователя. Во-вторых, мы добавим теги шаблона в представления, чтобы обеспечить динамическое взаимодействие с пользовательскими данными в коде.

Для работы с функцией urlencoded() сначала откройте файл app.js:

nano app.js

Перед функцией express.static() добавьте следующую строку:

...
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
...

Добавление этой функции позволит получить доступ к проанализированным данным POST из формы ввода. Мы указали true с опцией extended, чтобы обеспечить гибкость при анализе типов данных (включая такие вещи, как вложенные объекты). Пожалуйста, прочитайте документацию по функциям для получения дополнительной информации.

Сохраните и закройте файл.

Далее мы добавим функции шаблона в представления. Сначала установите пакет ejs с помощью npm install:

npm install ejs

Затем откройте файл sharks.html в папке views:

nano views/sharks.html

В разделе 3 предыдущей части мы открывали эту страницу, чтобы определить, как написать схему и модель Mongoose.

Теперь в макет из двух столбцов мы добавим третий столбец с формой, где пользователи смогут вводить информацию об акулах.

Для начала измените размеры существующих столбцов до 4, чтобы создать три столбца одинакового размера. Обратите внимание, что вам нужно будет внести это изменение в две строки <div class=»col-lg-6″>. Обе они должны выглядеть стать <div class=»col-lg-4″>

...
<div class="container">
<div class="row">
<div class="col-lg-4">
<p>
<div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
</p>
</div>
<div class="col-lg-4">
<p>
<div class="caption">Other sharks are known to be friendly and welcoming!</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
</p>
</div>
</div>
</div>
</html>

Читайте также: Введение в Bootstrap

Затем добавьте еще один столбец, который включает в себя именованную конечную точку для POST запроса с данными пользователя и тегами шаблона EJS для захвата этих данных. Этот столбец будет идти после закрывающих тегов </p> и </div> из предыдущего столбца и перед закрывающими тегами строки, контейнера и HTML-документа. Эти закрывающие теги уже есть в вашем коде; ниже они отмечены комментариями. Оставьте их на месте, просто добавьте следующий код для создания нового столбца:

...
</p> <!-- closing p from previous column -->
</div> <!-- closing div from previous column -->
<div class="col-lg-4">
<p>
<form action="/sharks/addshark" method="post">
<div class="caption">Enter Your Shark</div>
<input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
<input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
<button type="submit">Submit</button>
</form>
</p>
</div>
</div> <!-- closing div for row -->
</div> <!-- closing div for container -->
</html> <!-- closing html tag -->

В теге формы мы добавили конечную точку «/sharks/addshark» для ввода пользовательских данных и указали метод POST для их отправки. В форме будут поля «Shark Name» и «Shark Character».

Чтобы добавить пользовательский ввод в коллекцию, используются теги шаблонов EJS (<%=, %>) вместе с синтаксисом JavaScript (для связи записей пользователя с соответствующими полями в новом документе).

Читайте также: Объекты в JavaScript

Много дополнительной информации о тегах шаблонов EJS можно найти в документации EJS.

Полный контейнер со всеми тремя столбцами, включая столбец с формой ввода пользовательских данных, будет выглядеть следующим образом:

...
<div class="container">
<div class="row">
<div class="col-lg-4">
<p>
<div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
</p>
</div>
<div class="col-lg-4">
<p>
<div class="caption">Other sharks are known to be friendly and welcoming!</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
</p>
</div>
<div class="col-lg-4">
<p>
<form action="/sharks/addshark" method="post">
<div class="caption">Enter Your Shark</div>
<input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
<input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
<button type="submit">Submit</button>
</form>
</p>
</div>
</div>
</div>
</html>

Сохраните и закройте файл.

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

Скопируйте недавно измененный файл sharks.html в файл getshark.html:

cp views/sharks.html views/getshark.html

Откройте getshark.html:

nano views/getshark.html

Внутри файла нужно изменить столбец, который мы использовали для создания формы для ввода данных. Его нужно заменить столбцом, который будет отображать данные в нашей коллекции sharks. Опять же, ваш код будет находиться между тегами </p> и </div> из предыдущего столбца и закрывающими тегами строки, контейнера и HTML-документа. Не забудьте оставить эти теги на месте при добавлении следующего кода для столбца:

...
</p> <!-- closing p from previous column -->
</div> <!-- closing div from previous column -->
<div class="col-lg-4">
<p>
<div class="caption">Your Sharks</div>
<ul>
<% sharks.forEach(function(shark) { %>
<p>Name: <%= shark.name %></p>
<p>Character: <%= shark.character %></p>
<% }); %>
</ul>
</p>
</div>
</div> <!-- closing div for row -->
</div> <!-- closing div for container -->

Здесь используются теги шаблонов EJS и метод forEach() для вывода каждого значения в коллекции, включая последнюю добавленную информацию.

Весь контейнер со всеми тремя столбцами, включая столбец с коллекцией sharks, будет выглядеть следующим образом:

...
<div class="container">
<div class="row">
<div class="col-lg-4">
<p>
<div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
</p>
</div>
<div class="col-lg-4">
<p>
<div class="caption">Other sharks are known to be friendly and welcoming!</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
</p>
</div>
<div class="col-lg-4">
<p>
<div class="caption">Your Sharks</div>
<ul>
<% sharks.forEach(function(shark) { %>
<p>Name: <%= shark.name %></p>
<p>Character: <%= shark.character %></p>
<% }); %>
</ul>
</p>
</div>
</div>
</div>
</html>

Сохраните и закройте файл.

Чтобы приложение могло использовать созданные вами шаблоны, вам нужно добавить несколько строк в файл app.js. Откройте его снова:

nano app.js

Выше, где вы добавили функцию express.urlencoded(), добавьте следующие строки:

...
app.engine('html', require('ejs').renderFile);

app.set('view engine', 'html');

app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
...

Метод app.engine позволяет приложению сопоставить механизм шаблонов EJS с файлами HTML, а app.set определяет механизм представления по умолчанию.

Ваш файл app.js должен теперь выглядеть так:

const express = require('express');
const app = express();
const router = express.Router();
const db = require('./db');
const path = __dirname + '/views/';
const port = 8080;
router.use(function (req,res,next) {
console.log('/' + req.method);
next();
});
router.get('/',function(req,res){
res.sendFile(path + 'index.html');
});
router.get('/sharks',function(req,res){
res.sendFile(path + 'sharks.html');
});
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
app.use('/', router);
app.listen(port, function () {
console.log('Example app listening on port 8080!')
})

Теперь, когда вы создали представления, которые могут динамически работать с пользовательскими данными, пришло время создать маршруты вашего проекта, чтобы объединить ваши представления и логику контроллера.

2: Создание маршрутов

Последним шагом в объединении компонентов приложения будет создание маршрутов. Мы разделим маршруты по функциям, включая маршрут к целевой странице приложения и отдельный маршрут к странице Sharks. В маршруте sharks  мы интегрируем логику контроллера с представлениями, которые мы создали на предыдущем этапе.

Сначала создайте каталог routes:

mkdir routes

Затем откройте файл index.js в этом каталоге:

nano routes/index.js

Этот файл сначала импортирует объекты express, router и path, что позволяет определять маршруты, которые мы хотим экспортировать с объектом router, и динамически работать с путями файлов. Добавьте следующий код в верхнюю часть файла:

const express = require('express');
const router = express.Router();
const path = require('path');

Затем добавьте следующую функцию router.use, которая загружает функцию промежуточного программного обеспечения. Она будет регистрировать запросы маршрутизатора и передавать их в маршрут приложения:

...
router.use (function (req,res,next) {
console.log('/' + req.method);
next();
});

Сначала запросы в корень приложения будут направлены сюда, а отсюда пользователи будут перенаправляться на целевую страницу приложения, маршрут которой мы определим далее. Добавьте следующий код под функцией router.use, чтобы определить маршрут к целевой странице:

...
router.get('/',function(req,res){
res.sendFile(path.resolve('views/index.html'));
});

Когда пользователи посещают приложение, они в первую очередь должны попадать на целевую страницу index.html, которая находится в каталоге views.

Наконец, чтобы сделать эти маршруты доступными в виде импортируемых модулей в другом месте приложения, добавьте закрывающее выражение в конец файла (для экспорта объекта маршрутизатора):

...
module.exports = router;

В итоге получится такой файл:

const express = require('express');
const router = express.Router();
const path = require('path');
router.use (function (req,res,next) {
console.log('/' + req.method);
next();
});
router.get('/',function(req,res){
res.sendFile(path.resolve('views/index.html'));
});
module.exports = router;

Сохраните и закройте этот файл.

Затем откройте файл sharks.js, чтобы определить, как приложение должно использовать различные конечные точки и представления, которые мы создали для работы с входными данными:

nano routes/sharks.js

В верхней части файла импортируйте объекты express и router:

const express = require('express');
const router = express.Router();

Затем импортируйте модуль shark, который позволит вам работать с экспортированными функциями для контроллера:

const express = require('express');
const router = express.Router();
const shark = require('../controllers/sharks');

Теперь вы можете создавать маршруты, используя функции index, create и list, которые вы определили в файле контроллера sharks. Каждый маршрут будет связан с соответствующим методом HTTP: GET в случае рендеринга целевой страницы и возврата пользователю списка введенной информации, а POST в случае создания новой записи.

...
router.get('/', function(req, res){
shark.index(req,res);
});
router.post('/addshark', function(req, res) {
shark.create(req,res);
});
router.get('/getshark', function(req, res) {
shark.list(req,res);
});

Каждый маршрут использует связанную функцию в controllers/sharks.js, так как мы сделали этот модуль доступным, импортировав его в начале этого файла.

Закройте файл, прикрепив эти маршруты к объекту router и экспортировав их:

...
module.exports = router;

Готовый файл будет выглядеть так:

const express = require('express');
const router = express.Router();
const shark = require('../controllers/sharks');
router.get('/', function(req, res){
shark.index(req,res);
});
router.post('/addshark', function(req, res) {
shark.create(req,res);
});
router.get('/getshark', function(req, res) {
shark.list(req,res);
});
module.exports = router;

Сохраните и закройте файл.

Последнее, что нужно сделать, чтобы эти маршруты стали доступными для приложения, — добавить их в app.js. Откройте этот файл:

nano app.js

Ниже константы db добавьте следующий импорт для ваших маршрутов:

...
const db = require('./db');
const sharks = require('./routes/sharks');

Затем замените функцию app.use, которая в данный момент монтирует объект router, следующей строкой, которая будет монтировать модуль sharks:

...
app.use(express.static(path));
app.use('/sharks', sharks);
app.listen(port, function () {
console.log("Example app listening on port 8080!")
})

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

Окончательная версия вашего файла app.js будет выглядеть так:

const express = require('express');
const app = express();
const router = express.Router();
const db = require('./db');
const sharks = require('./routes/sharks');
const path = __dirname + '/views/';
const port = 8080;
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
app.use('/sharks', sharks);
app.listen(port, function () {
console.log('Example app listening on port 8080!')
})

Сохраните и закройте файл.

Теперь вы можете снова запустить tree, чтобы увидеть окончательную структуру вашего проекта:

tree -I node_modules

Структура проекта теперь будет выглядеть так:

├── Dockerfile
├── README.md
├── app.js
├── controllers
│   └── sharks.js
├── db.js
├── models
│   └── sharks.js
├── package-lock.json
├── package.json
├── routes
│   ├── index.js
│   └── sharks.js
└── views
.    ├── css
.    │   └── styles.css
.    ├── getshark.html
.    ├── index.html
.    └── sharks.html

Создав и установив все компоненты приложения, вы можете добавить тестовые данные в свою БД.

Если вы выполнили мануал по начальной настройке сервера, вам нужно будет настроить брандмауэр, поскольку в настоящее время он поддерживает только трафик SSH. Чтобы разблокировать трафик по порту 8080, введите:

sudo ufw allow 8080

Запустите приложение:

node app.js

Затем в браузере перейдите по адресу http://your_server_ip:8080. Вы увидите свою целевую страницу:

Want to Learn About Sharks?
Are you ready to learn about sharks?
Get Shark Info

Нажмите на кнопку Get Shark Info. Вы увидите страницу Shark Info с маленькой формой ввода Enter your shark в правой части экрана. Форма состоит из полей Shark Name и Shark Character.

Чтобы убедиться, что все работает правильно, добавьте в это поле информацию о любой акуле. Например, можно попробовать ввести в первое поле Megalodon Shark, а во второе — Ancient

Нажмите на кнопку Submit. Вы увидите, что добавленная вами информация появилась на странице.

Вы также увидите в своей консоли вывод, указывающий, что ваша информация была добавлена в коллекцию:

Example app listening on port 8080!
{ name: 'Megalodon Shark', character: 'Ancient' }

Если вы хотите создать новую запись, вернитесь на страницу Sharks и добавьте новую информацию.

Теперь у вас есть рабочее приложение, которое позволяет пользователям добавлять информацию об акулах.

Заключение

В этой серии вы создали простое приложение Node и интегрировали базу данных MongoDB, переписав логику приложения по архитектуре MVC. Это приложение может послужить хорошей основой для полноценного приложения CRUD.

Читайте также:

Много дополнительной информации о работе с MongoDB можно найти в нашем Информатории по тегу MongoDB.

Tags: , , , , ,