Node 为我们提供了一个无需依赖浏览器、能够直接与操作系统进行交互的 JavaScript 代码运行环境。
它可以直接用 JS 写后台程序,使前台开发者也能快速开发后台代码,即HTML+JavaScript+MongoDB;而无需像 Tomcat 那样,前台(HTML+Javascript)+ 后台(Java)+ 数据库(MySql)。
在 setTimeout 等待的 n 秒内,程序并没有阻塞,而是继续向下执行,这就是 Node.js 的异步非阻塞。
在实际的应用环境中,往往有很多 I/O 操作(例如网络请求、数据库查询等等)需要耗费相当多的时间,而 Node.js 能够在等待的同时继续处理新的请求,大大提高了系统的吞吐率。
Node 引入了三个新的全局对象:1)require
;2) exports
和 3)module
。
require 用于导入其他 Node 模块,其参数接受一个字符串代表模块的名称或路径,通常被称为模块标识符。具体有以下三种形式:
// 导入内置库或第三方模块
const os = require('os');
const express = require('express');
// 通过相对路径导入其他模块
const utils = require('./utils');
// 通过绝对路径导入其他模块
const utils = require('/home/xxx/MyProject/utils');
export
导出对象
// myModule.js
function add(a, b) {
return a + b;
}
// 导出函数 add
exports.add = add;
通过将 add 函数添加到 exports
对象中,外面的模块就可以通过以下代码使用这个函数。在 myModule.js 旁边创建一个 main.js,代码如下:
// main.js
const myModule = require('./myModule');
// 调用 myModule.js 中的 add 函数
myModule.add(1, 2);
在实际项目发布或部署时不需要用到。npm 会把所有开发依赖添加到 devDependencies 字段中。
用node搭建简单的静态服务器
使用node搭建服务器,简单的来说可以分为三步:
在新建的node-server文件夹中新建server.js:
//1.require http模块
var http = require('http')
//2.创建服务器,传入回调函数,作用是处理网页请求
var server = http.createServer(function (req, res) {
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.writeHead(200, 'OK')
res.write(`hello world
`)
res.end()
})
console.log('open http://localhost:8080')
//3.设置监听的端口
server.listen(8080)
打开终端,进入node-server文件夹,输入 node index.js
启动服务器
Node中间层
在前后端分离的前提下,我们可以在服务器(java)和浏览器(js)中间架一个中间层(node.js)
选择node做中间层的理由:使用前端熟悉的语言js,学习成本低;有良好的执行速度。
在前后端分离情况下,node中间层可以承担更多的责任:
代理:在开发环境,可以利用代理来解决最常见的跨域问题;在线上环境,可以利用代理,转发请求到多个服务端。
缓存:缓存其实是更靠近前端的需求,node中间层可以直接处理一部分缓存需求。
限流:node中间层可以针对接口或者路由做相应的限流。
路由:前端更需要掌握页面路由的权限和逻辑。
node简单强大,轻量可扩展。
简单:node使用的是js来进行编码
强大:非阻塞I/O(输入/输出),擅长高并发访问
轻量:前后端使用统一语言js
可扩展:可以轻松地应对多实例,多服务器架构,并且有非常多的第三方组件
通过npm可以安装和管理项目的依赖,并且能够指定依赖项的具体版本号,可以通过package.json文件来管理项目信息,配置脚本。
通过判断全局对象,如果是 window ,当前脚本就是运行在浏览器中;如果是 global 就是node环境中。
同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为;
异步方法调用一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中,整个过程,不会阻碍调用者的工作。
CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的。
AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难。
CMD规范与AMD规范很相似,都用于浏览器编程,可以很容易在Node.js中运行。
ES6 实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
1)引入Mongoose(Mongoose封装了MongoDB一些增删改查等常用方法,让Node操作MongoDB数据库更加容易)
2)使用 mongoose.connect() 方法连接到 MongoDB数据库
3)监听连接是否成功
4)连接成功后通过node,书写接口,对数据库进行增删改查
Express是目前流行的基于node.js运行环境的web开发框架,可以快速地搭建一个完整功能的网站。
安装: npm install express
首先安装 express-generator 脚手架(用来初始化 Express 项目):npm install -g express-generator
初始化 Express 项目命令:express vue-online-shop-backend
(当然也可以手动npm init
初始化)
启动项目:npm start
我们在平时所用到的一些网站、App,它们会将我们的数据进行保存,当我们关闭这些网站或者 App 后,下次打开还能看到我们之前的一些文字、视频记录。我们可以通过基于 Node.js 平台的 Express 框架实现后端服务,并且将数据存储在 MongoDB 中。这样我们的网站就能够记录用户的增删改等操作,并且无论以后什么时候打开,都能获取我们之前的记录。
用 Node.js 内置的 http 模块实现一个服务器
const http = require('http'); // 导入模块
const hostname = 'localhost'; // 指定主机名
const port = 3000; // 指定端口号
// 创建服务器
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('Hello World\n');
});
// 开启服务器
server.listen(port, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
上面的代码含义:
最后运行 server.js: node server.js
用浏览器打开 localhost:3000,可以看到 Hello World
可以发现,直接用内置的 http 模块去开发服务器有以下明显的弊端:
由此就引出了 Express 对内置 http 的两大封装和改进:
(1)更强大的请求(Request)和响应(Response)对象
Request 请求对象,通常我们习惯用 req 变量来表示。下面列举一些 req 上比较重要的成员:
req.body
:客户端请求体的数据,可能是表单或 JSON 数据req.params
:请求 URI 中的路径参数req.query
:请求 URI 中的查询参数req.cookies
:客户端的 cookiesResponse 响应对象,通常用 res 变量来表示,可以执行一系列响应操作,例如:
// 发送一串 HTML 代码
res.send('HTML String');
// 发送一个文件
res.sendFile('file.zip');
// 渲染一个模板引擎并发送
res.render('index');
Response 对象上的操作非常丰富,并且还可以链式调用:
// 设置状态码为 404,并返回 Page Not Found 字符串
res.status(404).send('Page Not Found');
(2)路由机制
客户端向服务器发起请求时包括两个元素:路径(URI)以及 HTTP 请求方法(包括 GET、POST 等等)。
路径和请求方法合起来一般被称为 API 端点(Endpoint)。
而服务器根据客户端访问的端点选择相应处理逻辑的机制就叫做路由。
在 Express 中,定义路由只需按下面这样的形式:
app.METHOD(PATH, HANDLER)
其中:
接下来,我们将开始用 Express 来实现一个web服务器(区别于上面的用 Node.js 内置的 http 模块 实现的服务器)
1 创建express-server文件夹并初始化项目
npm init
接着你可以一路回车下去(当然也可以仔细填),就会发现 package.json 文件已经创建好了。
2 添加 Express 项目依赖
npm install express
3 安装nodemon
nodemon 加速开发
Nodemon 是一款开发服务器,能够检测工作区代码的变化,并自动重启。通过以下命令安装 nodemon:
npm install nodemon --save-dev
这里我们将 nodemon 安装为开发依赖 devDependencies
,因为仅仅只有在开发时才需要用到。同时我们在 package.json 中加入 start
命令:
{
// ...
"scripts": {
"start": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
// ...
"devDependencies": {
"nodemon": "^2.0.12"
}
}
4 代码实现
创建server.js文件
const express = require('express');
const hostname = 'localhost';
const port = 3000;
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
// 调用listen方法开启服务器
app.listen(port, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
在上面的代码中,我们首先用 express()
函数创建一个 Express 服务器对象,然后用上面提到的路由定义方法 app.get
定义了主页 /
的路由,最后同样调用 listen
方法开启服务器。
从这一步开始,我们运行 npm start
命令即可开启服务器,并且同样可以看到 Hello World 的内容,但是代码却简单明了了不少。
提示:在运行 npm start
之后,可以让服务器一直打开着,编辑代码并保存后,Nodemon 就会自动重启服务器,运行最新的代码。
中间件
中间件并不是 Express 独有的概念。它是一种广为使用的软件工程概念(甚至已经延伸到了其他行业)。是指将具体的业务逻辑和底层逻辑解耦的组件。换句话说,中间件就是能够适用多个应用场景、可复用性良好的代码。
Express 的简化版中间件流程如下图所示:
首先客户端向服务器发起请求,然后服务器依次执行每个中间件,最后到达路由,选择相应的逻辑来执行。
有两点需要特别注意:
Express 中间件的定义
在 Express 中,中间件就是一个函数:
function someMiddleware(req, res, next) {
// 自定义逻辑
next();
}
三个参数中,req
和 res
就是前面提到的 Request 请求对象和 Response 响应对象;
而 next
函数则用来触发下一个中间件的执行。
注意:如果忘记调用 next
函数,并且又不直接返回响应时,服务器会直接卡在这个中间件不会继续执行下去!
在 Express 使用中间件有两种方式:全局中间件和路由中间件:
app.use
函数就可以注册中间件:app.use(someMiddleware);
app.get('/homepage', someMiddleware, (req, res) => {
res.send('Hello World');
});
那么用户只有在访问 /homepage
时,定义的 someMiddleware
中间件才会被触发,访问其他路径时不会触发。
编写中间件
实现第一个 Express 中间件:功能很简单,就是在终端打印客户端的访问时间、 HTTP 请求方法和 URI,名为 loggingMiddleware
。代码如下:
// ...
const app = express();
function loggingMiddleware(req, res, next) {
const time = new Date();
console.log(`[${time.toLocaleString()}] ${req.method} ${req.url}`);
next();
}
app.use(loggingMiddleware);
app.get('/', (req, res) => {
res.send('Hello World');
});
// ...
注:在中间件中写 console.log 语句是比较糟糕的做法,因为 console.log(包括其他同步的代码)都会阻塞 Node.js 的异步事件循环,降低服务器的吞吐率。在实际生产中,推荐使用第三方优秀的日志中间件,例如 morgan、winston 等等。
运行服务器,然后用浏览器尝试访问各个路径。这里我访问了首页(localhost:3000)和 /hello
(localhost:3000/hello,浏览器应该看到的是 404),可以看到终端控制台相应的输出:
[11/28/2019, 3:54:05 PM] GET /
[11/28/2019, 3:54:11 PM] GET /hello
我们只实现了一个功能很简单的中间件。实际上,中间件不仅可以读取 req 对象上的各个属性,还可以添加新的属性或修改已有的属性,能够很方便地实现一些复杂的业务逻辑(例如用户鉴权)。
用模板引擎渲染页面
Express 对当今主流的模板引擎(例如 Pug、Handlebars、EJS 等等)提供了很好的支持。
(模板引擎:只需理解成一个"升级版的 HTML 文档"即可)
将使用 Handlebars 作为模板引擎。首先添加 npm 包:
npm install hbs
创建 views 文件夹,用于放置所有的模板。然后在其中创建首页模板 index.hbs,代码如下:
<h1>Indexh1>
<a href="/contact">Goa>
contact.hbs:
<h1>contacth1>
在 server.js 中配置和使用模板:
// 指定模板存放目录
app.set('views', '/path/to/templates');
// 指定模板引擎为 Handlebars
app.set('view engine', 'hbs');
在使用模板时,只需在路由函数中调用 res.render
方法即可:
// 渲染名称为 hello.hbs 的模板
res.render('hello');
修改后的 server.js 代码如下:
// ...
const app = express();
app.set('views', 'views');
app.set('view engine', 'hbs');
// 定义和使用 loggingMiddleware 中间件 ...
app.get('/', (req, res) => {
res.render('index');
});
app.get('/contact', (req, res) => { // 这里添加了 GET /contact 的路由定义
res.render('contact');
})
// ...
使用子路由拆分逻辑
当我们的网站规模越来越大时,把所有代码都放在 server.js 中可不是一个好主意。“拆分逻辑”(或者说“模块化”)是最常见的做法,而在 Express 中,我们可以通过子路由 Router 来实现。
首先创建 routes 目录,用于存放所有的子路由。创建 routes/index.js 文件,代码如下:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('index');
});
router.get('/contact', (req, res) => {
res.render('contact');
});
module.exports = router;
创建 routes/api.js,代码如下:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ name: 'Ann', age: 12 });
});
router.post('/new', (req, res) => {
res.status(201).json({ msg: 'Hello' });
});
module.exports = router;
最后我们把 server.js 中老的路由定义全部删掉,替换成刚刚实现的两个 Router,代码如下:
const express = require('express');
const path = require('path');
const indexRouter = require('./routes/index');
const apiRouter = require('./routes/api');
const hostname = 'localhost';
const port = 3000;
const app = express();
// ...
app.use(express.static('public'));
app.use('/', indexRouter);
app.use('/api', apiRouter);
app.use('*', (req, res) => {
res.status(404).render('404', { url: req.originalUrl });
});
// ...
此处选择使用 express-generator 脚手架来初始化我们的 Express 项目:
npm install -g express-generator
初始化Express 项目:
express e-server
开启项目:
npm install
npm start
打开浏览器 http://localhost:3000/
可以看见初始好的项目效果。
通过 express-generator 初始化的项目代码中,我们在整个教程中只需要了解下面四个文件:
app.js
:Express 应用主文件bin/www
:用来开启服务器的脚本routes/index.js
:路由主文件views/index.ejs
:主页的模板文件,这里由于我们只打算实现 API 数据接口,所以不用关心与之前的 Express 教程不同的是,脚手架代码并没有把所有的路由都放在 app.js 中,而是根据不同的子应用(users、index)进行了拆分。
在 app.js 中:
开头是导入相关依赖,然后通过调用 express() 初始化 express 实例;接着我们设置了模板引擎为 ejs,以及模板引擎的存放目录,然后就是一系列中间件的加载使用,最后导出 express 实例,丢给 bin/www 脚本进行调用并启动服务器。
路由部分 routes/index.js
路由是 API 服务器的核心,我们对数据进行增删改查都需要访问特定的路由接口。
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
上面的代码,首先导入 express,然后使用其属性方法生成了一个 router 实例,接着定义了 get 这一 HTTP 方法来处理以 GET 方法访问我们服务器地址为 / 时如何进行处理,最后导出 index 路由。
⭐我们的 API 服务器实际上就是通过 HTTP 的各种方法(POST、DELETE、PUT、GET 等)访问我们定义的路由,进而对数据库进行相应的增删改查操作以获取我们期望的数据。
⭐提示:虽然 Express 也可以通过模板引擎展示用户界面,但是由于我的项目前端已经用 Vue 来实现了,所以不需要模板引擎。
解决数据持久化存储最流行的方案无疑是数据库,而 MongoDB 凭借其优异的性能、可扩展性和灵活的数据模式,从众多数据库产品中脱颖而出。
启动MongoDB后,可以通过 localhost:27017
进行访问。
安装 Mongoose
Mongoose封装了MongoDB一些增删改查等常用方法,让Node操作MongoDB数据库更加容易。
npm install mongoose
在app.js 文件中导入 mongoose ,并且通过 mongoose 提供的接口mongoose.connect 连接MongoDB 数据库:
const mongoose = require('mongoose'); // 导入mongoose
mongoose.connect(`mongodb://localhost:27017/test`); // 连接数据库
接着通过 npm start
运行服务器,我们就在 Express 中连接上了我们的 MongoDB 数据库,虽然现在还看不到任何效果,我们马上会编写路由来操作数据库来测试连接的有效性。
设计数据库的 Schemas 和 Models
我们要在服务器中通过 mongoose 与 MongoDB 数据库进行交互,需要定义 Schema
和 Model
。通过定义它们来告诉 mongoose 你需要的数据结构和对应的数据类型是什么。
创建 model/index.js 文件编写 Schema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const model = mongoose.model.bind(mongoose);
const ObjectId = mongoose.Schema.Types.ObjectId;
const textSchema = Schema({
id: ObjectId,
text: String,
createTime: {
type: Date,
default: Date.now
},
});
const Text = model("Text", textSchema);
module.exports = { Text };
Schema 接收一个 JavaScript 对象来描述我们需要的数据结构和对应的数据类型,除了我们熟知的像 String、Number 等数据类型外,ObjectId 是一个特殊的数据类型,我们用它来定义我们的单个 MongoDB 文档的主键,用于标志存储数据的唯一性。
接着通过 model 来创建对应的数据模型,然后导出创建好的数据模型。这里的 model 就是经典的 MVC 设计模式中的 Model。
完成 API 路由
路由是 Express 的关键组成部分,也是客户端与服务器进行交互的入口,在 Express 路由中接受两个参数:Request 和 Response,一个用来获取客户端的请求,一个用来发送给客户端服务器的响应。
在router下创建api.js文件。打开 app.js
文件,加入如下代码:
const api = require('./routes/api');
app.use('/api/v1', api);
导入了 api 路由,并定义了访问路径 /api/v1。所有访问 /api/v1 及其子路径如 /api/v1/xxx 都会激活 api 处理函数。在经典的 MVC 设计模式中,api 也被成为 Controllers 。
创建Controllers文件夹,下面创建texts.js文件:
具体的texts Controller
const Model = require('../model');
const { Text } = Model;
const textController = {
all(req, res) {
Text.find({}).sort({ _id: -1 })
.exec((err, texts) => res.json(texts))
},
byId(req, res) {
const idParams = req.params.id;
Text
.findOne({ _id: idParams })
.exec((err, text) => res.json(text));
},
create(req, res) {
const requestBody = req.body;
const newText = new Text(requestBody);
newText.save((err, saved) => {
Text
.findOne({ _id: newText._id })
.exec((err, text) => res.json(text))
})
},
update(req, res) {
const idParams = req.params.id;
let text = req.body;
Text.updateOne({ _id: idParams }, { ...text }, (err, updated) => {
res.json(updated);
})
},
remove(req, res) {
const idParams = req.params.id;
Text.findOne({ _id: idParams }).remove((err, removed) => res.json(idParams))
}
}
module.exports = textController;
接着编写 api Controllers:
const express = require('express');
const router = express.Router();
const textController = require('../../controllers/text');
router.get('/texts', textController.all);
router.get('/texts/:id', textController.byId);
router.post('/texts', textController.create);
router.put('/texts/:id', textController.update);
router.delete('/texts/:id', textController.remove);
module.exports = router;
现在 API 服务器就搭建完成了,可以通过 API 测试工具 postman 进行测试。
测试1:
<script src="./axios.min.js">script>
mounted() {
axios
.get('http://localhost:3000/api/v1/texts')
.then(response => (this.info = response))
.catch(function (error) { // 请求失败处理
console.log(error);
});
}
测试2 在vue中:
import axios from 'axios'
Vue.prototype.$axios = axios //全局注册,使用方法为:this.$axios
mounte() {
this.$axios
.get("http://localhost:3000/api/v1/texts")
.then((response) => console.log(response));
}
进入数据库管理模式 mongo
退出数据库管理模式 exit
显示所有的数据库列表 show dbs
创建数据库:use dbName
(如果数据库名不存在则创建,已存在就直接进入) use test
查看当前数据库:db
删除数据库:db.dropDatabase()
显示当前数据库中的所有集合:show collections
创建集合:db.tableName.insert({})
,通常在创建数据时自动创建集合
删除集合:db.tableName.drop()
新增数据:db.集合名.insert({BSON数据}) 如:db.user.insert({"name":"admin","age":20})
查询数据: db.集合名.find({条件对象}) db.foo.find()
- 查询所有
修改数据:db.集合名.update(查找对象, 修改结果)
删除数据:db.集合名.remove({})- 删除当前集合中的所有数据,
db.集合名.remove({“name”:“Ann”}) - 删除指定的数据
关系型数据库指采用了关系模型来组织数据的数据库。关系模型指的就是二维表格模型。
优点:
缺点:
主流的关系型数据库:Oracle,SQL Server,MySQL等
非关系型数据库以键值对存储。
优点:
缺点:不适用于持久存储
主流的非关系型数据库:Redis,MongoDB等
Redis为 key-value 数据库
key-value数据库的主要特点是具有极高的并发读写性能。
Key-value数据库是一种以键值对存储数据的数据库,可以将整个数据库理解为一个大的map,每个键都会对应一个唯一的值。
MongoDB为文档型数据库
文档可以很长、很复杂、甚至无结构。 mongodb是文档型数据库也是key-value数据库。
MongoDB 查询功能强大,可以在海量的数据中快速查询。
在node服务器端设置:
//解决跨域问题
app.use(async(ctx, next) => {
// 指定服务器端允许进行跨域资源访问的来源域。*表示允许任何域的JavaScript访问资源
ctx.set("Access-Control-Allow-Origin", "*");
// ...
await next();
});