JavaScript:创建一个简单的MEVN应用程序

这篇文章是关于什么的


本文的目的是展示如何创建基本的MEVN应用程序。 首字母缩写词MEVN
表示-MongoDB + Express.js + Vue.js +Node.js。 例如,它将被写成
单页应用程序,它包含由多个文本组成的表单
领域。 填写表单并发送数据时,服务器会将其写入数据库,并且
将客户端重定向到“谢谢”页面。

使用Ubuntu 18.10作为操作系统,将安装所有组件
表示相对于她。

必要条件


  • 熟悉HTML,CSS;
  • JavaScript的基础知识。

我们在出口处有什么


  • 完整的Fullstack应用程序;
  • 使用Express.js的CRUD操作和REST API;
  • 连接到MongoDB。

工作区准备


要开始开发应用程序,您需要安装一些工具。
整个项目的基础是Node.js及其NPM软件包管理器。 Node.js是一个运行时
JavaScript,其环境包括执行程序所需的一切,
用javascript编写。

您可以在这里安装它 。 应按照如下指示选择“稳定”版本
屏幕截图:

图片

您还可以使用NVM(节点版本管理器)-这是用于版本控制Node.js的便捷工具。 您可以使用以下命令从终端安装它:

env VERSION=`python tools/getnodeversion.py` make install DESTDIR=`nvm_version_path v$VERSION` PREFIX="" 

然后让nvm使用*版本*,例如:

 nvm use 10 

现在已经安装了Node.js,您可以使用node -v命令进行验证:

 node -v > 10.14.2 

接下来是MongoDB。 此DBMS被归类为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(命令行界面)-一个完整的系统,可以在Vue.js上快速开发。 它将提供交互式创建项目的机会,并提供更新和优化设置以使用框架的能力。 您可以通过指定-g(全局)标志来使用NPM安装它,以便将依赖项全局安装并可以在特定项目之外使用,即 全球:

 npm install -g @vue/cli 

现在,您已经安装了DBMS MongoDB和Node.js + NPM,剩下的工作就是决定使用文本编辑器了。 我将使用VS Code作为文本编辑器。 您可以选择自己喜欢的任何编辑器:Sublime,Atom甚至Notepad ++。

项目初始化


要创建新的Vue.js应用程序,我们将使用先前安装的Vue CLI。 create命令类似于vue create * app_name *:

 vue create mevn-app 

终端中会出现一个界面,可让您配置新应用程序的设置。 选择“手动选择功能”进行详细设置,进行以下列表:

图片

在应用程序生成结束时,将出现类似的消息:

图片

您可以使用上面的npm run serve命令启动应用程序,并查看Vue CLI生成了什么:

图片

此开始页面具有Vue路由器功能(顶部的两个链接),已安装的CLI插件以及文档,Vue生态系统项目和社交网络的链接。

因此,现在创建的项目的层次结构如下所示:

图片

  • node_modules-项目起作用所需的已安装依赖关系目录。 通常,它不会在git中建立索引,因为 它的体积有时会达到很大的尺寸。
  • package.json是一个项目初始化文件,它非常紧密地链接到node_modules目录。 它包含有关项目的信息(名称,作者,版本),运行NPM的已执行脚本以及node_modules中仅包含的所有已安装依赖项。 依赖关系由关键值“依赖关系”(生产中使用的依赖关系)和“ devDependencies”(开发中使用的依赖关系)表示。 即 主要需要此文件,以便仅使用npm i命令就可以将项目部署在任何计算机上。 您可以尝试删除node_modules目录,然后在项目的根目录中运行npm i命令:它将再次提取package.json中指定的所有必要依赖项。
  • package-lock.json是整个依赖关系树的快照。 事实是,程序包具有顶级依赖性。 安装时,这并不明显,但是您始终可以在package-lock.json中看到它。 因此,例如,安装过程中的引导程序包将拉动jquery包。 不会在package.json中指定jQuery,但仍将其作为引导程序依赖项安装,即 您无需在“依赖项”中另外指定jquery即可使引导程序正常运行。
  • babel.config.js是一个包含规则(预设)的文件,Babel通过该文件学习如何翻译代码。 Babel是代码传输器。 JavaScript语言具有您需要遵循的规范,以便代码在生产中正常运行。 但是通常并非所有的浏览器都设法在其解释器中实现新规范,并且某些规范根本没有实现。 为此,Babel存在:它将代码从规范转换为大多数浏览器可以理解的规范。 即 在开发过程中,您编写了一个代码,由于有了Babel,另一个代码才投入生产。 在我们的情况下,只有一个预设-'@ vue / app'。
  • postcss.config.js-PostCSS配置文件。 这是一个CSS后处理工具,可以通过许多很酷的方式来转换CSS,例如,自动添加前缀,检查是否符合代码设计标准等。 它是由Vue CLI自动安装的,到目前为止仅包含添加前缀的规则,这将确保跨浏览器应用程序。
  • browserslist.rc-一个文件,该文件确定设计用于应用程序开发的浏览器。 在我们的案例中,这是最近两个版本的浏览器,它们在全球范围内拥有超过5%的用户,但不包括低于版本8的Internet Explorer。
  • README.md-带有以Markdown编写的项目信息的文件-一种轻量级的标记语言,旨在编写最易读和易于编辑的文本。 通常,此文件包含项目描述,有关主软件包版本的信息,安装说明等。
  • src(源)-直接在其中进行开发的目录。 它包含所有书面代码,以及资产/目录,其中放置了scss / css,js,字体,图像等。 Vue.js使用Webpack进行构建:应用程序正常运行所需的所有代码都将打包到dist /目录内的单个vendor.js文件中。 值得将此目录添加到.gitignor,因为 组装后的应用程序将占用存储库中的额外空间。 像node_modules一样,dist /可以使用单个NPM命令进行编译。
  • public-该目录包含用于生成完成的应用程序的基本html模板,通常还包括其图标(favicon.ico)。

开发开始(前端)


由于我们使用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实例,并使用Vue类方法-。$ Mount(“#app”)将其安装到虚拟DOM级别的指定元素中。 在方法内部,您应该指定一行,其中包含基本html模板中指定的html元素的ID。

在router.js中,Vue导入在创建项目时Vue CLI安装的vue-router软件包以及路由中涉及的所有组件。 在创建路由实例时,通过将对象数组作为参数传递给Router类来进行路由:

 new Router({ routes: [ { path: '/', name: 'home', component: Home }, ... ] ) 

首先,让我们从App.vue中删除默认的Vue样式。 然后,您应该删除组件/目录以及HelloWorld.vue,将不再需要它们。 除了目录和组件本身之外,还应该在视图/ 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 + } ] }) 

现在,我们依靠路由系统来传递组件。 需要一个新的感谢组件。 与其创建一个新的,不如编辑About.vue并将其重命名为Thank.vue:

views / About.vue> views / Thank.vue:

 <template> - <div class="about"> + <div class="thanks"> <h1>Thank you for your record!</h1> </div> </template> 

并更改Home.vue:添加一个表单,该表单将发送诸如姓名,电子邮件,地址和性别之类的数据:

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收听事件;
  • 提交-这是给定的事件(提交仅适用于表单。例如,对于按钮,您可以应用click事件,而对于输入-输入或更改);
  • .prevent是一个事件修饰符,该修饰符调用preventDefault()js处理程序,该处理程序会暂停元素(在我们的情况下为表单)的浏览器事件。 表单的浏览器事件正在发送数据并重新加载页面,应避免这种情况。 此外,还有.stop(完全删除浏览器事件)、. once(仅执行一次方法)等修饰符。
  • sendData是处理事件时将调用的方法(对象内部的函数)。 请注意,仅当方法接受参数时,方括号()才应放置。

此外,在每个输入处都有一个v-model属性。 它与数据双向绑定表单元素(输入,选择,文本区域)。 也就是说,写入输入的值(其中v-model =“ someText”)将立即写入someText数据属性(如果存在)。

在导出的对象中,我们删除了components属性,因为 Home.vue没有子组件,但是我们添加了data方法。 该数据包含Vue数据对象。 值得注意的是,您不能只将data的值写为带有数据的对象-您肯定需要将值写为返回值的函数。 这是Vue组件的功能。
在我们的例子中,数据是输入值的名称,电子邮件,地址和性别。

除了数据之外,还出现了一个methods属性,其中包含使用数据的所有方法。 上面只提到了一种方法-sendData。 它与提交表单有关,目前仅在控制台中显示输入值。

组件的最后一部分是样式。 在此编写样式,以便表单正确显示。

现在打开我们的应用程序。 在我们面前的是应该填写的表格,并使用“发送”按钮发送数据。

图片

图片

发送时,页面不会重新加载,并且表单值也不会重置。 现在该看看浏览器控制台了(Ctrl + Shift + I):

图片

如果您在控制台中看到的是相同的东西,仅使用指定的值-恭喜,您编写了第一个完整的Vue应用程序。 如果没有,那么您应该检查每个步骤的正确性并得出肯定的结果。

现在,让我们继续使用Vue应用程序,继续在Node.js上开发服务器,以便该应用程序可以与之交换数据。

持续发展(后端)


首先,您需要安装服务器操作所需的npm软件包:

 npm install -S express morgan mongoose 

-S开关告诉npm将这些软件包添加到package.json中,作为依赖项数组的元素。 输出将是类似的消息:

图片

  • Express.js是Node.js的Web应用程序框架,旨在创建Web应用程序和API。 它是简约的,并包含大量插件。
  • Morgan-用于将HTTP请求记录到服务器的软件包。
  • Mongoose是在Node.js下创建的MongoDB的ORM(对象关系映射)。

然后,将具有以下层次结构的另一个目录添加到项目根服务器:

图片

server.js-这将是使用Node环境启动的Node.js服务器。 您可以通过在此处编写简单的代码来尝试使用Node,例如:

服务器/ server.js
 console.log('Hello, world!'); 

然后从项目的根目录使用node命令启动server.js,并指定文件的路径及其名称:

 node server/server.js > Hello, world! 

我们将清除server.js文件并开始开发。 首先,导入所有以前安装的软件包(express,morgan和mongoose),然后初始化Express应用程序:

服务器/ server.js

 + const express = require('express'); + const mongoose = require('mongoose'); + const morgan = require('morgan'); + const path = require('path'); + + const app = express(); 

如果您以前从未遇到过const:const,则可以使用两个变量声明语句之一(第二个let)代替ES6标准var。 它的特点是不能更改分配给变量的值。 对于具有更多变量值的变量,建议使用let。

require()是Node.js环境的功能,该功能实现了连接本机和npm各种模块的功能。 请注意,我们没有安装路径包,但我们将其导入-它已经包含在环境依赖项中。

const app = express()-Express应用程序的初始化。 接下来,我们将使用app变量,这将是我们的服务器。

接下来,您需要为我们的Express应用程序设置设置,但是由于它很小,因此您只需要设置一个参数-端口。 例如,取值为3000(或任何可用端口)。 在我们开始侦听端口之后,即 启动服务器:

服务器/ 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。在接收到该端口后,箭头回调函数将运行。 如果您以前从未遇到过箭头函数:箭头函数表示为()=> {},并且几乎与函数(){}完全相似,除了一个:箭头函数具有一个全局对象作为调用(this)函数的上下文(全局在Node.js环境中以及在浏览器环境中是Window),以及通常的函数本身,即 功能介绍 在这种情况下,箭头功能可以简化记录,因为 我们不会以任何方式使用它。 因此,作为回调函数,仅向控制台执行一条消息,指出服务器正在本地主机上运行:3000。 在字符串中写入$ {...}允许您向其中插入一个计算值,在本例中为app.get()函数的返回值。

现在,在浏览器中打开localhost:3000地址,您将看到消息“无法获取/”。 这意味着我们的服务器已经启动并且正在正常工作。 稍后,我们将显示Vue.js应用程序而不是此消息,但是现在,我们将建立与MongoDB数据库和中间件的连接:

服务器/ 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://本地主机:27017 / mevn-course”和对象{useNewUrlParser:true}。

useNewUrlParser用于与MongoDB 3.1.0+版本一起使用。 如果出于某种原因使用的版本低于3.1.0,则不应指定此参数。

.then和.catch是分别在实现和失败时返回Promise的方法。 在这些方法中,将调用一个回调函数,该函数将Promise的结果返回数据库对象db,然后将.catch的错误返回。 这两种方法都将信息打印到控制台:有关成功连接或有关错误的信息。

使用app.use(),为我们的应用程序安装了中间件。 这些功能可以访问应用程序的“请求-响应”循环中的请求对象(req),响应对象(res)和下一个中间处理功能(next)。 我们将使用Express内置的解析器作为中间件,该解析器随数据请求一起提供(在我们的示例中为json和urlencoded),以及先前安装的带有'dev'参数的morgan软件包,这意味着以“开发”模式登录。 现在,服务器可以接收带有json和urlencoded格式请求的传入数据,并记录所有传入请求。 再次启动该应用程序:

节点服务器/ server.js

 > [OK] Server is running on localhost:3000 > [OK] DB is connected 

现在,如果我们在浏览器中转到localhost:3000地址,则所有请求都将记录在控制台中,在这种情况下为GET请求:

图片

我们将从事Record模型的开发。 这是必需的,以便将数据以所需的格式(此格式称为Schema)发送到数据库。 我们从Vue应用程序发送的表单发送名称,电子邮件,地址和性别-所有这些都可以表示为字符串。 因此,数据库中的记录应包含“行”类型的4个字段。 创建模型:

服务器/模型/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); 

我们导入猫鼬包,并从猫鼬包中将Schema变量设置为Schema类的值。 符号“ const {Schema} = mongoose”在ES6中称为解构,等效于“ const Schema = mongoose.Schema”。 接下来,创建Schema类的实例,该实例的参数是一个具有记录属性及其数据类型名称的对象。
“ Module.exports = ...”是导出条目。 即 当我们导入该模块时,导入结果将是mongoose.model(“记录”,记录)。

创建模型后,您需要制作一个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;

Source: https://habr.com/ru/post/zh-CN457580/


All Articles