Pull to refresh

JavaScript: Создание простого MEVN-приложения

Reading time19 min
Views31K

О чем статья


Цель этой статьи — показать, как можно создать базовое 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”, как указано на
скриншоте:

image

Так же можно использовать 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-подобные документы и схему базы данных.

image

Для установки нужно выполнить последовательность команд:

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” для подробной настройки, составляем следующий перечень:

image

По окончании генерации приложения, должно появиться подобное сообщение:

image

Можно запустить приложение указанной выше командой npm run serve и посмотреть, что сгенерировал Vue CLI:

image

На этой стартовой странице присутствует функционал Vue Router (две ссылки в самом верху), установленные CLI плагины, а также ссылки на документацию, проекты экосистемы Vue и социальные сети.

Так выглядит сейчас иерархия созданного проекта:

image

  • 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.

image

image

При отправке страница не перезагружается, и значения формы не сбрасываются. Самое время посмотреть в консоль браузера (Ctrl + Shift + I):

image

Если вы видите в консоли примерно то же самое, только с указанными вами значениями — поздравляю, вы написали свое первое полноценное Vue-приложение. Если нет, то стоит проверить правильность выполнения каждого шага и прийти к положительному результату.

Пока что оставим Vue-приложение, как есть и перейдем к разработке сервера на Node.js, чтоб приложению было, с чем обмениваться данными.

Продолжение разработки (backend)


Для начала следует установить необходимые npm пакеты для работы сервера:

npm install -S express morgan mongoose

Ключ -S говорит npm, что данные пакеты нужно добавить в package.json, как элемент массива dependencies. На выводе будет подобное сообщение:

image

  • Express.js — фреймворк web-приложений для Node.js, спроектированный для создания веб-приложений и API. Он минималистичен и включает большое число подключаемых плагинов.
  • Morgan — пакет для логирования HTTP-запросов к серверу.
  • Mongoose — это ORM (Object-Relational Mapping) для MongoDB сделанная под Node.js.

Затем добавим в корень проекта еще один каталог — server, со следующей иерархией:

image

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:

image

Займемся разработкой модели Записи. Это нужно для того, чтоб данные в базу данных отправлялись в нужном формате (Этот формат называется 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 сейчас возвращает пустой массив:

image

В таком случае выполним POST-запрос по этому же адресу, в теле которого укажем валидные данные для записи в базу:

image

Видим, что ответ от сервера пришел в формате JSON, как мы и указывали, это сообщение
{“state”: “success”}. Выполняем предыдущий GET-запрос снова:

image

Все вышло, вы можете самостоятельно протестировать оставшиеся операции над данными (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

image

Затем перезапускаем сервер и в браузере переходим по адресу localhost:3000/. Точно, теперь тут отображается наше Vue-приложение. Заполним форму и отправим данные нажатием Send. Смотрим в консоль:

image

В качестве данных возвращается ответ с сервера. Это значит, что запись добавлена, и наше приложение работает, как надо.

Теперь внесем последние штрихи в приложене: редирект при отправке формы на страницу благодарности; возможность перейти со страницы благодарности на главную, удаление ссылок навбара, удаление 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, письмо отправляется.

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

image

А так неудачная:

image

Итог


На этом разработка приложения подошла к концу. Что мы имеем:

  • опыт разработки Vue-приложения;
  • опыт разработки Node + Express сервера;
  • опыт работы с MongoDB;
  • опыт работы с npm;
  • умение организовать редирект страниц;
  • умение организовать email-рассылку с сервера;
  • умение отправлять с клиента и принимать на сервере http-запросы;
  • понимание CRUD и REST API;
Tags:
Hubs:
Total votes 11: ↑10 and ↓1+9
Comments7

Articles