О чем статья
Цель этой статьи — показать, как можно создать базовое MEVN-приложение. Акроним MEVN
означает — MongoDB + Express.js + Vue.js + Node.js. В качестве примера будет написано
одностраничное приложение, которое содержит форму, состоящую из нескольких текстовых
полей. При заполнении формы и отправке данных, сервер будет записывать их в базу данных, а
клиент редиректить на страницу “Спасибо”.
В качестве операционной системы используется Ubuntu 18.10, установка всех компонентов будет
указана относительно нее.
Необходимые требования
- Знания HTML, CSS;
- Базовые знания JavaScript.
Что имеем на выходе
- Полноценное Fullstack-приложение;
- CRUD операции и REST API при помощи Express.js;
- Подключение к MongoDB.
Подготовка рабочего пространства
Для начала разработки приложения, необходимо установить некоторые инструменты.
Основа всего проекта — Node.js и его пакетный менеджер NPM. Node.js это — среда выполнения
JavaScript, окружение которого включает в себя все, что вам нужно для выполнения программы,
написанной на JavaScript.
Установить можно здесь. Следует выбирать версию “Stable”, как указано на
скриншоте:
Так же можно использовать NVM (Node Version Manager) — это удобный инструмент для управления версиями Node.js. Установить его из терминала можно командой:
env VERSION=`python tools/getnodeversion.py` make install DESTDIR=`nvm_version_path v$VERSION` PREFIX=""
Затем выполнить nvm use *version*, например:
nvm use 10
Теперь Node.js установлен, проверить это можно командой node -v:
node -v
> 10.14.2
Далее идет MongoDB. Это СУБД, классифицированая как NoSQL, использует JSON-подобные документы и схему базы данных.
Для установки нужно выполнить последовательность команд:
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
Когда MongoDB установлена, запустить ее в фоновом режиме можно так:
sudo systemctl start mongod
Vue.js — фронтенд-фреймворк для создания пользовательских интерфейсов. Vue полностью подходит для создания одностраничных приложений (SPA).
Для работы с Vue.js есть официальная CLI (Command Line Interface) — полноценная система для быстрой разработки на Vue.js. Она предоставит возможность интерактивного создания проекта, а также предоставит возможность обновления и оптимальные настройки для работы с фреймворком. Установить можно с помощью NPM, указав флаг -g (global), чтоб зависимость установилась глобально и могла использоваться вне конкретного проекта, т.е. глобально:
npm install -g @vue/cli
Теперь, когда имеется установленная СУБД MongoDB и Node.js + NPM, осталось определиться с текстовым редактором. У меня в качестве текстового редактора будет использоваться VS Code. Можно выбрать любой редактор на свой вкус: будь то Sublime, Atom или даже Notepad++.
Инициализация проекта
Для создания нового приложения Vue.js, воспользуемся установленной ранее Vue CLI. Команда создания выглядит, как vue create *app_name*:
vue create mevn-app
В терминале появляется интерфейс, позволяющий задать настройки для нового приложения. Выбираем “Manually select features” для подробной настройки, составляем следующий перечень:
По окончании генерации приложения, должно появиться подобное сообщение:
Можно запустить приложение указанной выше командой npm run serve и посмотреть, что сгенерировал Vue CLI:
На этой стартовой странице присутствует функционал Vue Router (две ссылки в самом верху), установленные CLI плагины, а также ссылки на документацию, проекты экосистемы Vue и социальные сети.
Так выглядит сейчас иерархия созданного проекта:
- node_modules — директория установленных зависимостей, необходимых для работы проекта. Обычно, она не индексируется в git, т.к. ее объем порой достигает очень крупных размеров.
- package.json — это файл инициализации проекта, который очень плотно связан с директорией node_modules. В нем содержится информация о проекте (название, автор, версия), прописаны скрипты выполняющиеся NPM, а также все установленные зависимости, которые как раз содержатся в node_modules. Зависимости обозначены значениями ключей “dependencies” (зависимости используемые на продакшн) и “devDependencies” (зависимости, используемые при разработке). Т.е. этот файл нужен прежде всего для того, чтоб проект можно было развернуть на любой машине, одной лишь командой npm i. Можно попробовать удалить директорию node_modules, а затем выполнить команду npm i в корне проекта: она заново подтянет все необходимые зависимости, которые указаны в package.json.
- package-lock.json — моментальный снимок всего дерева зависимостей. Дело в том, что пакеты имеют зависимости верхнего уровня. При установке это не заметно, но всегда можно посмотреть в package-lock.json. Так, например, пакет bootstrap при установке будет тянуть за собой пакет jquery. Jquery не будет указан в package.json, но все равно установится, как зависимость bootstrap, т.е. не придется дополнительно указывать jquery в “dependencies” для полноценной работы bootstrap.
- babel.config.js — это файл, содержащий правила (пресеты), через которые Babel узнает, как нужно транслировать код. Babel — это транспайлер кода. У языка JavaScript есть спецификации, которым нужно следовать, чтоб код корректно работал на продакшн. Но часто не все браузеры успевают реализовать новую спецификацию в своих интерпретаторах, а некоторые спецификации вообще не реализуют никогда. Для этого и существует Babel: он транслирует код из какой либо спецификации в спецификацию, понятную большинству браузеров. Т.е. при разработке вы пишете один код, а, благодаря Babel, на продакшн выходит другой. В нашем случае пресет всего один — '@vue/app'.
- postcss.config.js — файл конфигурации PostCSS. Это инструмент пост-обработки CSS, который может трансформировать ваш CSS множеством крутых способов, например, автоматическое добавление префиксов, проверка соблюдения стандарта оформления кода и многими другими. Он автоматически устанавливается Vue CLI и содержит пока только правила для добавления префиксов, что обеспечит кроссбраузерность приложения.
- browserslist.rc — файл, определяющий, на какие браузеры рассчитана разработка приложения. В нашем случае, это последние 2 версии браузеров, имеющих более 5% пользователей во всем мире, исключая Internet Explorer ниже 8 версии включительно.
- README.md — файл с информацией о проекте, написанный на Markdown — облегчённый язык разметки, созданный с целью написания наиболее читаемого и удобного для правки текста. Обычно, этот файл содержит в себе описание проекта, информацию о версиях основных пакетов, инструкцию по установке и т.п.
- src (source) — директория, внутри которой происходит непосредственно разработка. Здесь содержится весь написанный код, а также каталог assets/, куда помещаются файлы scss/css, js, шрифты, изображения и т.д. Vue.js использует Webpack для сборки: весь код, необходимый для корректной работы приложения, будет упакован в один файл vendor.js внутри каталога dist/. Стоит добавить этот каталог в .gitignor, т.к. собранное приложение занимает лишнее пространство в репозитории. Как и node_modules, dist/ можно собрать с помощью одной команды NPM.
- public — каталог, в котором содержится базовый html-шаблон для генерации готового приложения и, обычно, его иконка (favicon.ico).
Начало разработки (frontend)
Так как мы пользуемся Vue CLI, вместо разделения кода на отдельные html, css и js, создаются файлы vue. В этом нет ничего сверхъестественного: файлы vue подразумевают следующую структуру:
<template>
* HTML *
</template>
<script>
* JavaScript *
</script>
<style>
* Stylesheet *
</style>
Это сделано для более удобного оформления компонентов Vue и является всего лишь синтаксическим сахаром. Именно файлы с таким форматом обрабатываются Babel для последующего выпуска в продакшн.
В каталоге src/, помимо компонентов Vue, есть также файлы main.js и router.js.
В main.js Vue импортирует все необходимые пакеты, а затем создает новый экземпляр Vue и монтирует его в указанный элемент, на уровне Virtual DOM, с помощью метода класса Vue — .$mount(”#app”). Внутри метода следует указать строку с id html-элемента, указанного в базовом html-шаблоне.
В router.js Vue импортирует пакет vue-router, который был установлен Vue CLI при создании проекта и все компоненты, которые участвую в маршрутизации. Маршрутизация происходит путем передачи, в качестве аргумента, массива объектов в класс Router, при создании его экземпляра:
new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
...
]
)
Первым делом уберем заданные Vue стили по-умолчанию из App.vue. Затем следует удалить каталог components/ вместе с HelloWorld.vue, они больше не понадобятся. Помимо самого каталога и компонента, следует удалить его импорт внутри views/Home.vue, где он используется:
views/Home.vue:
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
- <HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
- import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'home',
components: {
- HelloWorld
}
}
</script>
Теперь от приложения остались только пара ссылок и их представления. Содержимое этих страниц тоже не понадобится, но не обязательно их удалять, можно просто изменить, но сначала нужно отредактировать файл маршрутизации:
router.js:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
+ import Thanks from './views/Thanks.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
- {
- path: '/about',
- name: 'about',
- component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
- }
+ {
+ path: '/thanks',
+ name: 'thanks',
+ component: Thanks
+ }
]
})
Теперь переходим к компонентам, опираясь на систему маршрутизации. Нужен новый компонент Thanks. Вместо создания нового, отредактируем About.vue и переименуем в Thanks.vue:
views/About.vue > views/Thanks.vue:
<template>
- <div class="about">
+ <div class="thanks">
<h1>Thank you for your record!</h1>
</div>
</template>
И изменим Home.vue: добавим форму, которая будет отправлять такие данные, как Имя, Email, Адрес и Пол:
views/Home.vue:
<template>
<div class="home">
- <img alt="Vue logo" src="../assets/logo.png">
+ <form @submit.prevent="sendData">
+ <div class="form-control">
+ <label for="name">Name</label>
+ <input v-model="name" id="name" type="text">
+ </div>
+ <div class="form-control">
+ <label for="email">Email</label>
+ <input v-model="email" id="email" type="email">
+ </div>
+ <div class="form-control">
+ <label for="address">Address</label>
+ <input v-model="address" id="address" type="text">
+ </div>
+ <div class="form-control">
+ <label for="gender">Gender</label>
+ <span>Male <input v-model="gender" id="gender" type="radio" value="male"></span>
+ <span>Female <input v-model="gender" id="gender" type="radio" value="female"></span>
+ </div>
+ <input type="submit" class="send" value="Send">
+ </form>
</div>
</template>
<script>
export default {
name: 'home',
- components: {}
+ data: function () {
+ return {
+ name: '',
+ email: '',
+ address: '',
+ gender: '',
+
+ },
+ methods: {
+ sendData() {
+ console.log(this.name, this.email, this.address, this.gender);
+
+
}
</script>
+ <style>
+ .form-control {
+ padding: 5px;
+ }
+ .form-control label {
+ display: block;
+
+ .send {
+ margin: 5px
+
+ </style>
Первое, что может ввести в заблуждение — @submit.prevent=«sendData». В Vue события прослушиваются с помощью атрибута v-on:, но так как этот атрибут при разработке используется достаточно часто, было придумано его сокращение — @. Т.е. эту строку можно представить, как v-on:submit.prevent=«sendData».
Разберем, что это вообще значит:
- v-on или @ указывает Vue на прослушивание какого-либо события;
- submit — и есть данное событие (submit применимо только к формам. К кнопке, например можно применить событие click, а к инпуту — input или change);
- .prevent — модификатор события, который вызывает js-обработчик preventDefault(), который приостанавливает браузерное событие для элемента — в нашем случае, формы. Браузерное событие для формы — это отправка данных и перезагрузка страницы, чего следует избежать. Также бывают такие модификаторы, как .stop (полное удаление браузерного события), .once (выполнение метода лишь один раз) и другие.
- sendData — метод (функция внутри объекта), который будет вызван при обработке события. Обратите внимание на то, что скобки () к методу следует ставить только в том случае, если он принимает параметры.
Далее, у каждого инпута встречается атрибут v-model. Он двунаправленно связывает элементы формы (input, select, textarea) с данными. Т. е., значение записанное в инпут, где v-model=”someText”, будет сразу же записано в свойство данных someText, если оно существует.
В экспортируемом объекте мы убираем свойство components, т.к. Home.vue не будет иметь дочерних компонентов, но добавляем метод data. В data содержится объект данных Vue. Стоит заметить, что нельзя просто записать значение data, как объект с данными — обязательно нужно записать значение, как функцию, которая его возвращает. Это особенность Vue-компонентов.
В нашем случае, данные — это значения инпутов name, email, address и gender.
Помимо data, появилось свойство methods, содержащее все методы для работы с данными. В нем записан только один метод — sendData, который упоминался выше. Он привязан к отправке формы и пока что просто выводит в консоль значения инпутов.
И последняя часть компонента — style. Сюда записаны стили, чтоб форма отображалась корректно.
Откроем теперь наше приложение. Перед нами форма, которую следует заполнить и отправить данные с помощью кнопки Send.
При отправке страница не перезагружается, и значения формы не сбрасываются. Самое время посмотреть в консоль браузера (Ctrl + Shift + I):
Если вы видите в консоли примерно то же самое, только с указанными вами значениями — поздравляю, вы написали свое первое полноценное Vue-приложение. Если нет, то стоит проверить правильность выполнения каждого шага и прийти к положительному результату.
Пока что оставим Vue-приложение, как есть и перейдем к разработке сервера на Node.js, чтоб приложению было, с чем обмениваться данными.
Продолжение разработки (backend)
Для начала следует установить необходимые npm пакеты для работы сервера:
npm install -S express morgan mongoose
Ключ -S говорит npm, что данные пакеты нужно добавить в package.json, как элемент массива dependencies. На выводе будет подобное сообщение:
- Express.js — фреймворк web-приложений для Node.js, спроектированный для создания веб-приложений и API. Он минималистичен и включает большое число подключаемых плагинов.
- Morgan — пакет для логирования HTTP-запросов к серверу.
- Mongoose — это ORM (Object-Relational Mapping) для MongoDB сделанная под Node.js.
Затем добавим в корень проекта еще один каталог — server, со следующей иерархией:
server.js — это будет Node.js-сервер, который запускается с помощью среды Node. Вы можете попробовать Node, записав сюда простой код, например:
server/server.js
console.log('Hello, world!');
Затем запустите server.js с помощью команды node из корня проекта, указав путь к файлу и его имя:
node server/server.js
> Hello, world!
Очистим файл server.js и приступим к разработке. Сначала следует импортировать все установленные ранее пакеты (express, morgan и mongoose), а также инициализировать Express-приложение:
server/server.js
+ const express = require('express');
+ const mongoose = require('mongoose');
+ const morgan = require('morgan');
+ const path = require('path');
+
+ const app = express();
Если ранее вы не сталкивались с const: const — один из двух операторов объявления переменной (второй let), пришедших на замену var, стандарта ES6. Его особенность в том, что значение присвоенное переменной нельзя изменить. Для переменных с изменяемыми в дальнейшем значениями рекомендуется использовать let.
require() — функция среды Node.js, реализующая возможность подключения различных модулей, как собственных, так и npm. Заметьте, мы не устанавливали пакет path, но импортируем его — он уже входит в зависимости среды.
const app = express() — инициализация приложения Express. Далее мы будем работать с переменной app, это и будет наш сервер.
Далее следует установить настройки для нашего Express-приложения, но так как оно небольшое, нужно задать всего один параметр — port. Возьмем, например, значение 3000 (или любой доступный порт). После запустим прослушивание порта, т.е. запустим сервер:
server/server.js
...
+ app.set('port', 3000);
+
+ app.listen(app.get('port'), () => {
+ console.log(`[OK] Server is running on localhost:${app.get('port')}`);
+ });
Метод set просто добавляет в определенный объект указанное свойство с указанным значением, которое потом можно получить путем обращения к имени свойства методом get. Именно это мы и делаем, когда устанавливаем прослушивание приложения: app.get(‘port’) вернет заданное ранее значение 3000. После получения порта идет стрелочная callback-функция. Если ранее вы не сталкивались со стрелочными функциями: стрелочная функция обозначается, как () => {} и является практически полной аналогией function () {}, за исключением одного: стрелочная функция имеет в качестве контекста вызова функции (this) глобальный объект (Global в среде Node.js и Window в среде браузера), а обычная функция саму себя, т.е. Function. В данной ситуации стрелочная функция просто упрощает запись, т.к. мы никак не используем this. Итак, в качестве callback-функции просто выполняется сообщение в консоль о том, что сервер запущен по адресу localhost:3000. Запись ${...} внутри строки позволяет вставлять в нее вычисляемое значение, в нашем случае возвращаемое значение функции app.get().
Теперь, открыв в браузере адрес localhost:3000, вы увидите сообщение “Cannot GET /”. Это значит, что наш сервер запустился и работает корректно. Позже мы сделаем так, чтоб вместо данного сообщения выводилось наше Vue.js-приложение, а пока установим соединение с базой данных MongoDB и middleware:
server/server.js
...
app.set('port', 3000);
+
+ mongoose.connect('mongodb://localhost:27017/mevn-course', { useNewUrlParser: true })
+ then(db => console.log('[OK] DB is connected'))
+ catch(err => console.error(err));
+
+ app.use(express.json());
+ app.use(express.urlencoded({extended: false}));
+ app.use(morgan('dev'));
...
С помощью mongoose.connect() происходит подключение к базе данных. Заметьте, что сама MongoDB должна быть активна перед последующим подключением к ней. В этот метод передаются два параметра — адрес базы и набор настроек в виде объекта. В нашем случае это строка “mongodb://localhost:27017/mevn-course” и объект { useNewUrlParser: true }.
useNewUrlParser используется для работы с MongoDB версии 3.1.0+. Если по какой-то причине вы используете версию ниже, чем 3.1.0, не следует указывать этот параметр.
.then и .catch — методы, возвращающие Обещание при выполнении и отказе соответственно. Внутри этих методов вызывается callback-функция, которая в качестве результата Обещания для .then возвращает объект базы данных — db, а для .catch — ошибку. Оба этих метода выводят в консоль информацию: либо об успешном соединении, либо об ошибке.
С помощью app.use() устанавливается middleware для нашего приложения. Это функции, имеющие доступ к объекту запроса (req), объекту ответа(res) и к следующей функции промежуточной обработки (next) в цикле “запрос-ответ” приложения. Мы будем использовать в качестве middleware встроенные в Express парсеры приходящих с запросами данных (в нашем случае это json и urlencoded) и установленный ранее пакет morgan с параметром ‘dev’, который обозначает логирование в режиме “разработка”. Теперь сервер может получать приходящие с запросами данные в формате json и urlencoded и логировать все приходящие запросы. Снова запустим приложение:
node server/server.js
> [OK] Server is running on localhost:3000
> [OK] DB is connected
Теперь, если мы перейдем в браузере по адресу localhost:3000, в консоли будет происходить логирование всех запросов, в данном случае запроса GET:
Займемся разработкой модели Записи. Это нужно для того, чтоб данные в базу данных отправлялись в нужном формате (Этот формат называется Schema). Наша форма из Vue-приложения отправляет Имя, Email, Адрес и Пол — это все может быть представлено, как строка. Значит запись в базе данных должна содержать 4 поля типа “строка”. Создаем модель:
server/models/Record.js
+ const mongoose = require('mongoose');
+ const { Schema } = mongoose;
+
+ const Record = new Schema({
+ name: String,
+ email: String,
+ address: String,
+ gender: String,
+ });
+
+ module.exports = mongoose.model('Record', Record);
Мы импортируем пакет mongoose и присваиваем переменной Schema значение класса Schema из пакета mongoose. Запись “const { Schema } = mongoose” называется деструктуризацией в ES6 и эквивалентна “const Schema = mongoose.Schema”. Дальше создается экземпляр класса Schema, в качестве параметра которого передается объект с названиями свойств записи и их типами данных.
“module.exports = …” — это запись экспорта. Т.е. когда мы будем импортировать этот модуль, результатом импорта будет mongoose.model('Record', Record).
Когда модель создана, нужно сделать файл API-маршрутизации. В качестве архитектурного стиля взаимодействия компонентов будет использоваться REST. REST API определяет набор функций, к которым разработчики могут совершать запросы и получать ответы. Взаимодействие происходит по протоколу HTTP. Методы вызова REST API — это методология CRUD (Create, Read, Update, Delete), т.е. GET, POST, PUT, DELETE. Добавим код в файл маршрутизации:
server/models/Record.js
+ const express = require('express');
+ const router = express.Router();
+
+ const Record = require('../models/Record');
+
+ router.get('/', async (req, res) => {
+ res.json(await Record.find());
+ });
+
+ router.post('/', async (req, res) => {
+ const record = new Record(req.body);
+ await record.save();
+ res.json({state: 'success'});
+ });
+
+ router.get('/:id', async (req, res) => {
+ res.json(await Record.findById(req.params.id));
+ });
+
+ router.put('/:id', async (req, res) => {
+ await Record.findByIdAndUpdate(req.params.id, req.body);
+ res.json({state: 'updated'});
+ });
+
+ router.delete('/:id', async (req, res) => {
+ await Record.findByIdAndRemove(req.params.id);
+ res.json({state: 'deleted'});
+ });
+
+ module.exports = router;
Мы импортируем пакет Express и создаем объект маршрутизатора. Также импортируем модуль модели Record, для взаимодействия с базой данных. Далее просто описывается маршрутизация. Конструкция async/await — это относительно новый способ написания асинхронного кода. Раньше подобный код писали, пользуясь callback-функциями и обещаниями. Благодаря async/await, асинхронный код становится похожим на синхронный, да и в его поведении появляются черты такого кода, весьма полезные в некоторых ситуациях, в которых обещаниями пользоваться было, по разным причинам, неудобно.
Вместо:
router.get('/', req, res) => {
res.json(Record.find()
.then((data) => {
return data;
}));
});
Имеем:
router.get('/', async (req, res) => {
res.json(await Record.find());
});
Методы у router такие, как .get(), .post(), .put() и .delete() дают серверу понимание, как следует обрабатывать определенные запросы. Внутри методов присутствует асинхронно выполняющаяся callback-функция с двумя параметрами req — объект запроса и res — объект ответа. В каждом методе, кроме метода POST, происходит асинхронное обращение к базе данных, с помощью методов модели Record, таких, как find(), findById(), findByIdAndUpdate(), findByIdAndRemove(). Затем идет ответ от сервера в формате JSON, например, res.json({state: ‘success’}). У метода POST обработка происходит по-другому: сначала создается экземпляр класса Record, параметром которого является тело запроса, который приходит из Vue-приложения, затем экземпляр асинхронно сохраняется в базу данных, с помощью метода save(), и только тогда отправляется ответ в формате JSON. Также, обратите внимание на последние три запроса: get, put и delete — в адресе они имеют :id. Это значит, все, что будет записано в адрес после “/”, будет доступно, как значение req.params.id и представлено, как строка. таким образом мы можем обращаться по id.
Наверное, возникает вопрос, по какому id мы будем обращаться к записям в базе данных, если Schema содержит только поля name, email, address и gender? Ответ: MongoDB сама создает для каждой записи идентификатор, который будет иметь имя _id.
Модель и маршрутизация написаны, осталось только импортировать нужный модуль в server.js:
server/server.js
...
app.use(morgan('dev'));
+
+ app.use('/api/records', require('./routes/records'));
Эта запись означает, что написанная нами маршрутизация будет начинаться с /api/records. Т.е, чтоб добавить новую запись нужно отправить POST-запрос с телом, содержащим валидные данные (имя, email, адрес и пол) по адресу localhost:3000/api/records.
Убедимся в работе сервера — запустим его и протестируем API. Я использую для этого Postman. Это полноценный инструмент для тестирования API.
GET-запрос по адресу localhost:3000/api/records сейчас возвращает пустой массив:
В таком случае выполним POST-запрос по этому же адресу, в теле которого укажем валидные данные для записи в базу:
Видим, что ответ от сервера пришел в формате JSON, как мы и указывали, это сообщение
{“state”: “success”}. Выполняем предыдущий GET-запрос снова:
Все вышло, вы можете самостоятельно протестировать оставшиеся операции над данными (UPDATE, DELETE или GET one).
Разработка backend-части подошла к концу, осталось внести последний штрих — обозначить статические файлы для отображения по адресу localhost:3000/. Добавим код:
server/server.js
...
app.use('/api/records', require('./routes/records'));
+ app.use('/', express.static(path.join(__dirname, '../dist')));
Продолжение разработки (frontend)
Вернемся к Vue-приложению, теперь есть API, с которым оно может взаимодействовать. Поэтому, не теряя времени, изменим метод sendData, написанный ранее, но сперва, с помощью npm установим пакет axios — npm i -S axios.
views/Home.vue
...
<script>
+ import axios from 'axios';
...
methods: {
+ async sendData() {
- console.log(this.name, this.email, this.address, this.gender);
+ console.log(await axios({
+ url: 'http://localhost:3000/api/records',
+ method: 'post',
+ data: {
+ name: this.name,
+ email: this.email,
+ address: this.address,
+ gender: this.gender
+
+ }));
}
}
Axios — клиентская библиотека для отправления запросов на сервер, которая использует обещания по умолчанию. Тут мы применяем уже знакомую конструкцию async/await. В функцию axios передается объект — набор опций (url, method, data). При выполнении метода запрос отправляется.
Теперь выполним команду npm run build. С помощью нее Vue соберет готовое приложение в каталог dist, который мы указали в качестве статических файлов для сервера:
npm run build
Затем перезапускаем сервер и в браузере переходим по адресу localhost:3000/. Точно, теперь тут отображается наше Vue-приложение. Заполним форму и отправим данные нажатием Send. Смотрим в консоль:
В качестве данных возвращается ответ с сервера. Это значит, что запись добавлена, и наше приложение работает, как надо.
Теперь внесем последние штрихи в приложене: редирект при отправке формы на страницу благодарности; возможность перейти со страницы благодарности на главную, удаление ссылок навбара, удаление console.log():
views/Thanks.vue
<template>
<div class="thanks">
<h1>Thank you for your record!</h1>
+ router-link to="/">Home</router-link>
</div>
</template>
views/Home.vue
...
async sendData() {
- console.log(await axios({
+ await axios({
url: 'http://localhost:3000/api/records',
method: 'post',
data: {
name: this.name,
email: this.email,
address: this.address,
gender: this.gender
}
- }));
+ });
+ this.$router.push('thanks');
}
...
App.vue
...
<div id="app">
- <div id="nav">
- <router-link to="/">Home</router-link> |
- <router-link to="/thanks">About</router-link>
- </div>
<router-view/>
</div>
...
Наше Vue-приложение полностью готово, собираем его с помощью npm run build, перезапускаем сервер, проверяем работоспособность.
Завершение разработки (backend)
Было бы неплохо чтоб сервер отправлял уведомление на почту, при добавлении каждой новой записи клиентом в базу данных. Для этого будем использовать сервис для отправки писем для Node.js — Nodemailer. Установим его: npm install -S nodemailer. Теперь, когда Nodemailer установлен, добавим функционал в server.js:
server/routes/records.js
...
const router = express.Router();
+ const nodemailer = require('nodemailer');
...
router.post('/', async (req, res) => {
const record = new Record(req.body);
await record.save();
+ const output = `
+ <p>You have a new message from MEVN-course:</p>
+ <ul>
+ <li>name: ${req.body.name}</li>
+ <li>email: ${req.body.email}</li>
+ <li>address: ${req.body.address}</li>
+ <li>gender: ${req.body.gender}</li>
+ </ul>
+ `;
+ let transporter = nodemailer.createTransport({
+ host: 'smtp.gmail.com',
+ port: 587,
+ secure: false,
+ auth: {
+ user: 'your_email@gmail.com',
+ pass: 'your_password'
+
+ });
+ let mailOptions = {
+ from: '"MEVN-course " <your_email@gmail.com>',
+ to: 'some_email@gmail.com',
+ subject: `MEVN-course | New message`,
+ text: req.body.name,
+ html: output
+ };
+ transporter.sendMail(mailOptions, (error, info) => {
+ if (error) {
+ return console.log(error);
+
+ console.log('Message sent: %s', info.messageId);
+ console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
+ });
res.json({state: 'success'});
});
Разберемся, что тут происходит. Во-первых, импортируется установленный ранее пакет nodemailer. Во-вторых, меняется метод post так, чтоб помимо сохранения записи в базу данных, сервер еще и отправлял уведомление на почту. Сначала создается переменная output, в которую записывается html-разметка. Затем создается переменная transporter, с помощью метода createTransport объекта nodemailer, в который передается объект — набор опций для аутентификации почтового аккаунта: имя хоста, порт, а также логин и пароль. Заметьте, что если вы используете Gmail, помимо просто аутентификации, надо будет разрешить использование почтового ящика сторонними приложениями в настройка вашего Gmail-аккаунта. Далее создается переменная mailOptions, в которую записывается объект — опции письма: от кого отправлено письмо, кому отправлено, тема письма и его тело. И наконец, с помощью метода sendMail объекта transporter, письмо отправляется.
Перезагружаем сервер и тестируем. Если данные для аутентификации верны, а в настройках аккаунта разрешено использование почтового ящика сторонними приложениями, то письмо отправится, и в консоль выведется сообщение об успешной отправке, если же нет, ошибка так же будет указана в консоли. Так выглядит удачная отправка:
А так неудачная:
Итог
На этом разработка приложения подошла к концу. Что мы имеем:
- опыт разработки Vue-приложения;
- опыт разработки Node + Express сервера;
- опыт работы с MongoDB;
- опыт работы с npm;
- умение организовать редирект страниц;
- умение организовать email-рассылку с сервера;
- умение отправлять с клиента и принимать на сервере http-запросы;
- понимание CRUD и REST API;