Создание MEVN-приложения (Часть 1/2)

Создание full-stack приложения на основе Vue.js и Express.js (+MongoDB)

Valery Semenenko
devSchacht

--

Перевод статьи “Build full stack web apps with MEVN Stack [Part 1/2]”. С разрешения автора Aneeta Sharma.

Мы в нашей CloudFactory всегда стремимся идти в ногу со временем. И даже не смотря на тот факт, что наша фирма является в первую очередь Ruby-компанией, мы любим инвестировать в изучение новых технологий.

У нас стояла задача выбрать фронтенд-фреймворк для будущего full-stack проекта. Выбор стоял между MEAN и MERN, и поскольку фреймворк Vue.js — это что-то новое и интересное, то мы захотели его попробовать и дали зеленый свет MEVN.

Акроним MEVN означает — MongoDB + Express.js + Vue.js + Node.js. Цель этой статьи — показать, как можно создать базовое MEVN-приложение на стеке технологий MongoDB/Express.js/Vue.js/Node.js.

Необходимые требования

— Базовые знания JavaScript
— Концепции REST и CRUD
— Установленные Node.js и NVM
— Установленная MongoDB

Что ожидается получить

— Full-stack приложение MEVN
— CRUD-операции при помощи Express.js
— Подключение к MongoDB (мы будем использовать Mongoose)

В данном руководстве используются следующие версии пакетов:

— MongoDB v3.0.5
— Express.js v4.15.4
— Vue.js v2.4.2
— Node.js v8.5.0

В статье будет описан процесс создания каркаса приложения на стеке MEVN. Работа с базой данных MongoDB будет описана во второй части данного руководства. Исходный код создаваемого приложения расположен в репозитории — MEVN-boilerplate.

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

Итак, приступим!

Развертывание проекта

Для начала создадим директорию с будущим приложением:

$ mkdir mevn-application
$ cd mevn-application

В директорию с проектом необходимо создать поддиректорию для фронтенд-части приложения. Для этого воспользуемся консольной утилитой vue-cli фреймворка Vue.js.

Эта утилита устанавливается глобально в систему, через менеджер пакетов npm:

$ npm i -g vue-cli

Сгенерируем директорию client при помощи команды:

$ vue init webpack client

При запуске данная команда задаст серию вопросов о будущем приложении — имя проекта, имя автора проекта, описание проекта, использование eslint, использование тестов и так далее. При желании можно выбрать значения по-умолчанию путем нажатия клавиши Enter.

Примерный вид процесса выполнения команды vue-cli представлен на изображении ниже:

Примечание переводчика: в моем приложении mevn-application не установлена поддержка unit и e2e тестирования, так как это значительно утяжеляет размер проекта; к тому же возможности тестирования не будут использоваться в данном руководстве.

Теперь необходимо перейти в готовую директорию client и запустить внутри нее команду npm install для установки всех зависимостей проекта, перечисленных в файле client/package.json.

Примечание переводчика: в проекте mevn-application мною использовался менеджер пакетов yarn; это является личным предпочтением и не более того:

$ cd client
$ yarn install
$ yarn run dev

Последняя команда yarn run dev запускает локальный сервер по адресу http://localhost:8080/#/ и автоматически запускает браузер по-умолчанию по этому же адресу. В результате в окне браузера будет отображена стартовая страница проекта на Vue.js:

Базовая заготовка фронтенд-части будущего приложения готова. Теперь нужно вернуться немного назад и создать бэкенд-часть приложения на основе Express.js:

$ cd ..
$ mkdir server
$ cd server

Выполним инициализацию серверной части проекта путем запуска команды yarn init. Будет также задана серия вопросов о проекте и в результате сформируется файл package.json.

Также давайте создадим директорию src, внутри которой будут находиться все файлы будущего сервера. В частности, создадим в папке src файл index.js, который будет главным файлом сервера:

$ mkdir src
$ touch src/index.js

Добавим поддержку автоматической перезагрузки сервера при каждом изменении файлов проекта. Для этого воспользуемся популярным пакетом nodemon и добавим его как зависимость для разработки:

$ yarn add nodemon --dev

В файле server/package.json добавим секцию scripts и пропишем туда команду для nodemon, которая заставит этот пакет отслеживать изменения всех файлов проекта с расширением js внутри директории src:

"scripts": {
"start": "nodemon --ext js --watch src"
}

В результате файл package.json будет выглядеть таким образом:

{
"name": "server",
"version": "1.0.0",
"description": "server-part",
"main": "src/index.js",
"author": "Moe Green",
"license": "MIT",
"private": true,
"scripts": {
"start": "nodemon --ext js --watch src"
},
"devDependencies": {
"nodemon": "^1.14.10"
}
}

Теперь проверим работу серверной части приложения — для этого добавим в файле index.js строку console.log(“Hello World”) и запустим файл server/src/index.js командой:

$ yarn start

Примечание переводчика: автор оригинальной статьи использует команду npm start, что ничего не меняет.

Если все было сделано правильно, то пакет nodemon успешно запустится и в консоли будет выведено сообщение из файла:

Настройка серверной части

Теперь настало время приступить к настройке сервера. Для этого установим пакет express.js как зависимость проекта:

$ yarn add express

Затем установим дополнительные пакеты для разработки сервера. Пакет morgan для ведения логов, пакет body-parser для парсинга приходящих со стороны клиента данных, пакет cors для задействования CORS:

$ yarn add morgan body-parser cors

В результате файл package.json примет такой вид:

{
"name": "server",
"version": "1.0.0",
"description": "server-part",
"main": "src/index.js",
"author": "Moe Green",
"license": "MIT",
"private": true,
"scripts": {
"start": "nodemon --ext js --watch src"
},
"dependencies": {
"body-parser": "^1.18.2",
"cors": "^2.8.4",
"express": "^4.16.2",
"morgan": "^1.9.0"
},
"devDependencies": {
"nodemon": "^1.14.10"
}
}

В файле src/index.js выполним подключение установленных пакетов, а также запустим их на выполнение как middleware:

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const morgan = require('morgan')
const app = express()app.use(morgan('combined'))
app.use(bodyParser.json())
app.use(cors())

В конце файла src/index.js запустим сервер express на локальном порту 8081. Для этого предварительно создадим в директории src поддиректорию config с файлом конфигурации config.js внутри. Этот файл будет содержать все необходимые переменные и константы для сервера; в частности — номер локального порта:

$ mkdir config
$ touch config/config.js

Содержание файла config/config.js:

module.exports = {
port: 8081
}

Подключим конфигурационный файл config/config.js в главный файл сервера src/index.js:

const config = require('./config/config')

… и наконец “включим” сервер express:

app.listen(process.env.PORT || config.port,
() => console.log(`Server start on port ${config.port} ...`))

В итоге на данном этапе разработки сервер express будет выглядеть таким образом:

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const morgan = require('morgan')
const config = require('./config/config')
const app = express()app.use(morgan('combined'))
app.use(bodyParser.json())
app.use(cors())
app.listen(process.env.PORT || config.port,
() => console.log(`Server start on port ${config.port} ...`)

Серверная часть приложения запущена на порту 8081 (localhost:8081). Фронтенд-часть приложения запущена на порту 8080 (localhost:8080).

Создание первого маршрута

Теперь начинается самое интересное — давайте создадим для сервера первый маршрут (endpoint) и пусть это будет путь до страницы со всеми записями (posts). Для начала мы просто протестируем, правильно ли сервер отвечает на наши запросы:

app.get('/posts', (req, res) => {
res.send(
[{
title: "Hello World!",
description: "Hi there! How are you?"
}]
)
})

Если теперь мы “постучимся” на сервер по адресу http://localhost:8081/posts из Postman или из браузера, то в ответ должны получить сообщение:

Отлично — сервер express работает и правильно отвечает на наши запросы!

Настройка связи между frontend- и backend частями

Настало время “связать” обе части нашего приложения. Сделать так, чтобы фронтенд-часть (client) смогла посылать запросы на бэкенд-часть (server) и получать от сервера ответы.

Для этого воспользуемся популярным пакетом axios. Перейдем в клиентскую часть приложения (client) и выполним там установку этого пакета как зависимость проекта:

$ cd client
$ yarn add axios

В директории client/src создадим поддиректорию services с файлом api.js внутри. В этом файле мы подключим библиотеку axios, а затем экспортируем ее из этого файла как функцию с предустановленной настройкой — базовым адресом запроса по-умолчанию. Теперь каждый раз, как axios будет “стучаться” на сервер, он будет “идти” по этому адресу:

import axios from 'axios'export default () => {
return axios.create({
baseURL: 'http://localhost:8081'
})
}

В директории services создадим еще один файл PostsService.js, в котором подключим файл api.js как модуль. В итоге внутри модуля PostsService.js мы можем пользоваться готовым заранее настроенным axios. Экспортируем модуль PostsService.js как объект, у которого будет целый ряд методов — каждый метод для определенного случая.

Одним из таких случаев на данный момент у нас является факт получения записей (posts) с сервера. Для этого создадим метод fetchPosts:

import api from '@/services/api'export default {
fetchPosts () {
return api().get('posts')
}
}

На первый взгляд функция fetchPosts может показаться непонятной и обескураживающей. Но на самом деле здесь все просто.

api() — это вызов на исполнение возвращаемой модулем api функции. Эта запись равносильна записи axios.get(“posts”). Передача аргумента “posts” также может вызвать вопрос, но дело в том, что axios умеет “склеивать” адреса, поэтому в итоге получим такой адрес — http://localhost:8081/posts.

В файле клиентских маршрутов src/routes/index.js добавим маршрут для страницы (компонента) PostsPage, на которой будут отображаться все записи (posts), полученные с сервера express:

import Start from '@/components/pages/StartPage'
import Posts from '@/components/pages/PostsPage'
const routes = [
{
path: '/',
name: 'Start',
component: Start
},
{
path: '/posts',
name: 'Posts',
component: Posts
}
]
export default routes

… и подключим созданный маршрут в индексный файл src/router/index.js маршрутизатора router клиента:

import Vue from 'vue'
import Router from 'vue-router'
import routes from '@/routes'
Vue.use(Router)export default new Router({
mode: 'history',
routes
})

Нам осталось создать сам компонент PostsPage.vue по пути components/pages/PostsPage.vue. Но предварительно мы установим еще один пакет — bootstrap для быстрой и правильной стилизации Vue-компонентов:

$ yarn add bootstrap

Подключать данный плагин будем через систему плагинов. Создадим поддиректорию src/plugins и в ней файл bootstrap.js, отвечающий за настройку самого Bootstrap. В нашем случае ничего настраивать не будем, а просто импортируем установленный bootstrap:

import 'bootstrap/dist/css/bootstrap.css'

… и затем подключим его в src/main.js:

import Vue from 'vue'
import App from './App'
import router from './router'
import '@/plugins/bootstrap'
Vue.config.productionTip = false/* eslint-disable no-new */new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})

Примечание переводчика: данная система подключения плагинов была подмечена мною здесь — vue-2-boilerplate.

Осталось создать сам компонент PostsPage.vue (переводчик данной статьи большой поклонник шаблонизатора Pug, поэтому при создании Vue-компонентов везде и всюду будет такая “экзотическая” разметка):

Что здесь происходит? Во-первых, в шаблоне template компонента определяем две секции — одну section.panel.panel-success для отображения записей (posts,) если они есть (“пришли” с сервера); другую — section.panel.panel-danger, если записей нет.

В скриптах script компонента PostsPage.vue мы подключаем модуль PostsService, чтобы воспользоваться методом fetchPosts:

import PostsService from '@/services/PostsService'

Затем в компоненте создаем метод getPosts, который через async/await вызовет на исполнение метод fetchPosts объекта PostsService. Результат будет записан в массив this.posts. И наконец мы повесим метод getPosts на хук mounted, чтобы он вызывался на исполнение каждый раз, как компонент PostsPage.vue будет смонтирован в DOM.

Ну и в секции section.panel.panel-success данные из массива this.posts будут “разбрасываться” по таблице через директиву v-for.

Напоследок стоит обратить внимание на использование директивы v-if. В официальной документации демонстрируется сочетание двух директив — v-if + v-else.

Однако, в моем коде прописано другое сочетание — v-if=”posts.length” + !v-if=”posts.length”.

Сделано это для большей практичности данного примера, ибо - “A v-else element must immediately follow a v-if or a v-else-if element — otherwise it will not be recognized”.

Теперь, если запустить клиентскую часть yarn run dev и перейти в браузере по адресу http://localhost:8080/posts, то мы должны получить такой результат:

Все хорошо — на странице записей нет, потому что мы их не получили с сервера, так и должно быть. Сервер не вернул записи (posts), так как он не подключен к базе данных MongoDB и не может получить их оттуда.

Подключением к базе данных MongoDB и операциями CRUD мы займемся в следующей части данного руководства.

--

--