# Express

# 1.框架介绍

Express 是一个简洁而灵活的 Node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用。

Express和Node.js的关系就像jQuery和原生js的关系,框架只是对原生js做二次封装,提供更便捷的方式给开发者开发应用。

# 2. Express安装与使用

# 2.1 安装

通过npm命令安装express

npm install express

# 2.2 使用Express创建服务端

先回顾使用nodejs创建服务器

var http = require("http");
http.createServer(function(req, res) {
    // 设置响应头Content-type响应中文内容
  	res.setHeader("Content-type", "text/plan;charset=utf-8");
    res.write("Node服务器创建成功");
    res.end();
  }).listen(8080);

再看看使用Express创建服务器

// 引入express框架
const express = require("express");
// 通过构造函数创建服务器
const app = express();
// 监听8080端口
app.listen(8080);
// 监听请求和设置请求回调,就算返回中文也不需要设置响应头,因为框架已经帮我们处理了
app.get("/", (req, res) => res.send("你好,express"));

对比Nodejs和Express框架创建服务器的代码,Nodejs是使用http模块的createServer方法创建,而Express通过构造器创建,他们同样需要监听端口和设置请求回调处理函数,但是express在处理中文的时候更加智能。

查看代码

# 2.3 接收get和post请求

Express给我们提供了非常方便的处理请求的方法,通过app.Method('请求路径',回调函数)处理。

下面代码演示如何定一个get和post请求

const express = require("express");
const app = express();
app.listen(8080);
app.get("/", (req, res) => res.send("你好,express"));
// 监听getUser请求
app.get("/getUser", (req, res) => {
  res.send("给你一个学生");
});

// 定义一个/register的post请求
app.post("/register", (req, res) => {
  res.send("收到注册请求");
});

# 2.3.1 GET方法获取参数

假设访问URL为/getUser?name=tom需要获取name的值,可以通过req.query获取,res.query返回的是一个对象

// 访问路径为/getUser?name=tom
app.get("/getUser", (req, res) => {
  console.log(req.query) // 输出{name: 'tom'}
  res.send("给你一个学生");
});

# 2.3.2 POST方法获取参数

假设我们对/register发起post请求,请求参数也是name=tom ,我们同样可以使用旧的接收方式。

但是express提供了更强大的post请求处理中间件 body-parser

// 以前处理post参数的方式,在express同样适用
app.post("/register", (req, res) => {
  let chuck = "";
  req.on("data", data => {
    chuck += data;
  });
  req.on("end", () => {
    console.log(chuck);
  });
  res.send("收到注册请求");
});

使用body-parser中间件处理

1、因为body-parser是一个中间件,不是express自带的,使用前需要先安装

npm install body-parser

2、配置使用

// 服务端代码
// 加载body-parser中间件
const bodyParser = require('body-parser')
// 调用app.use方法传递中间件,让中间件处理post请求参数
app.use(bodyParser.urlencoded());
// 接收请求并读取参数,注意post请求参数放在req.body中
app.post("/register", (req, res) => {
  console.log(req.body.name); // 输出tom
  res.send("收到注册请求");
});
//客户端代码
$.post('/register', { name: 'tom' }, (data) => {
  console.log(data) // 输出“收到注册请求”
})

查看代码

# 2.4 中间件的基本概念(了解)

在自来水的处理过程中,中间的每一个处理环节,可以叫做中间件! 中间件就是用来处理原材料的,那么,在自来水处理过程中,处理的原料是水;在express中,中间件处理的原料是 request 对象 和 response 对象; middware

概念:中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量。

经过中间件的处理之后,express就向 req 和 res ,对象身上,挂在了一些好用的方法和属性,我们在进行开发的时候可以直接调用。中间件的常见功能包括:

  • 执行任何代码。
  • 修改请求和响应对象。
  • 终结请求-响应循环。
  • 调用堆栈中的下一个中间件。
  • ......

在2.3.2中我们处理POST请求就是利用了中间件实现的,bodyParser把接收到的参数经过处理,再交给下一个中间件处理。

app.use(bodyParser.urlencoded());

# 2.5 设置静态资源

在学习express前,如果用户访问一个资源,我们需要通过fs模块读取文件再返回给客户端

// 以前处理静态资源的方法  
if (url === '/' || url === '/index.html') {
    fs.readFile(path.join(__dirname, '../views/index.html'), (err, data) => {
      if (err) {
        res.end(404);
      } else {
        res.end(data);
      }
    });
  }

如果我们应用比较复杂有不同页面,图片,样式等资源组成,这样处理显得麻烦和不灵活。

express提供了专门用于处理静态资源的中间件express.static('静态目录')

// 同样使用app.use方法,"/static"表示监听访问的路径,当出现匹配,就让express.static中间件处理
// express.static("public")的public参数指向本地静态文件目录
app.use("/static", express.static("public"));

image-20200713115516577

配置完毕后,只要我们输入http://127.0.0.1:3000/static/文件名即可访问到public目录下的文件了。

通过下面的访问方式,就能请求到静态资源了 http://127.0.0.1:3000/static/banner.png http://127.0.0.1:3000/static/index.js

扩展知识

如果把所有资源都放在public目录,会显得乱糟糟的,资源不好管理。那么你可以把样式、图片资源在public目录,html页面在views目录。通过下面规则配置配置多个静态目录

// 配置静态资源访问规则
app.use("/static", express.static("public"));
// 配置静态页面访问
app.use(express.static("views"));

查看代码

# 2.6 路由中间件

u=3144687803,1856116354&fm=26&gp=0

路由器应该大家都有一个,因为我们现在电子设备越来越多,每个电子设备都需要接入网络,但是运营商(电信、联通)只给我们提供了一条网线,这时候需要路由器把一个网线分配给多个设备。

那我们就可以看出,路由器的作用是给我们分配网络资源的。

# 2.6.1 Express中的路由

看回2.3小节中代码,我们编写了一个get和post请求的路由,这就是最简单的路由,用来分配请求不同页面的处理方式。

const express = require("express");
const app = express();
app.listen(8080);
app.get("/", (req, res) => res.send("你好,express"));
// 监听getUser请求
app.get("/getUser", (req, res) => {
  res.send("给你一个学生");
});

// 定义一个/register的post请求
app.post("/register", (req, res) => {
  res.send("收到注册请求");
});

根据上面代码,我们可以认为路由其实就是用来监听请求路径,分配处理逻辑。但是如果都通过app实例监听页,会导致整个页面非常臃肿。

还记得MVC设计模式吗,在开发中为了让代码降低耦合性,我们常常把负责某一功能的代码放到一个单独文件。同样的,负责路由的代码,也是可以放到一个独立文件的,我们一般把这个文件叫router.js

// router.js代码
const express = require("express");
// 调用Router方法获取router实例
const router = express.Router();
// 把以前app.get的写法改成router.get
router.get("/", (req, res) => res.send("你好,express"));

router.get("/getUser", (req, res) => {
  res.send("给你一个学生");
});

router.post("/register", (req, res) => {
  res.send("收到注册请求");
});
// 导出router
module.exports = router;

// index.js代码
const express = require("express");
const app = express();
// 引入router
const router = require("./router");

app.listen(8080);
// 调用app.use方法应用路由
app.use(router);

image-20200714112149063

查看代码

# 2.7 异常处理

异常表示程序执行过程中出现非预期情况,例如下面读取对象异常,会出现Uncaught TypeError: Cannot read property 'name' of undefined

const student = { name: 'tom' }
console.log(student.child.name) // 执行异常 Uncaught TypeError: Cannot read property 'name' of undefined

为了捕获这个异常,我们常用try catch语法

try {
  const student = { name: 'tom' }
  console.log(student.child.name)
} catch(e) {
	console.log('出现异常啦!!')
}

# 2.7.1 Express异常处理

但是所有代码块都用try catch包裹,会让代码显得臃肿,Express给我们提供了统一异常处理的方法

// 记得放在路由后面
app.use((err, req, res, next) => {
  res.status(500).send({ code: 500, msg: "服务器异常" });
});

我们可以定义一个路由,故意去触发错误

router.get("/error", (req, res) => {
  // 这段代码会出现错误
  const student = { name: "tom" };
  console.log(student.child.name);
});

当客户端请求/error地址,就会得到{"code":500,"msg":"服务器异常"}

image-20200714112439473

# 2.7.2 404 资源找不到异常

上一小节介绍了如何处理代码执行异常,服务端往往还有一种异常就是访问不存在的资源,也就是我们常见的404错误。

// 处理资源找不到异常
app.use((req, res) => {
  res.status(404).send({ code: 404, msg: "资源找不到" });
});

image-20200714113853809

查看代码

# 2.7.3 设置跨域

app.all('*', function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    next();
});

# 2.8 模版渲染

在Ajax阶段,我们学习了art-template渲染模版,它的作用是方便我们把数据与html模版结合。

下面图片演示客户端渲染的顺序,浏览器首先请求student.html页面,再发送ajax请求http:127.0.0.1:3001/getStudents接口获取学生数据,浏览器接收到数据之后,使用art-template 把页面和数据整合在一起。

但是客户端渲染有两个问题,第一个问题:需要发送两次请求,第一次请求页面,第二次ajax请求数据。第二个问题:客户端渲染由于第一次请求是没有数据的,导致搜索引擎没法收录页面,也就是SEO问题。所以需要用到服务端渲染技术解决。

image-20200714150758921

# 2.8.1 服务端渲染

服务端渲染意思是把页面渲染这步骤放在服务端,减少客户端二次请求。

下图是服务端渲染的流程图,可以看到页面渲染被移动到服务端处理了。

image-20200714151436922

# 2.8.2 Express实现服务端渲染

1、安装

npm install --save art-template
npm install --save express-art-template

2、配置模版引擎

const express = require("express");
const app = express();
app.listen(8080);

// 给express增加art模版解析引擎
app.engine("art", require("express-art-template"));
// 因为express有多种引擎,这里要设置需要用到哪个模版引擎
app.set("view engine", "art");

//模版引擎默认目录为views,如果你要修改可以调用下面命令,把目录改为template
// app.set("views", "./template");

// 定义路由,当访问首页的时候进入下面回调
app.get("/", function(req, res) {
  // 调用render方法会触发模版引擎
  // 第一个参数index.art为views/index.art文件
  // 第二个参数是传递给模版的数据
  res.render("index.art", {
    users: [{ name: "tom", age: 18 }, { name: "joke", age: 28 }]
  });
});

3、使用art-template编写模版

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <p>学生信息</p>
    {{each users}} 
      姓名:{{$value.name}} 
      性别:{{$value.age}}
    {{/each}}
  </body>
</html>

image-20200714161455937

查看代码