# Express 框架
- express 是一个用于快速搭建网站服务器的nodejs框架,作者是TJ。
- express 中提供了很多网站中的常用功能,比如路由、上传、静态资源、模版等。
- express 的升级版是koa,koa的升级版是koa2,核心区别是express是大而全的框架,而koa是轻薄的框架,koa中用到某个功能还得单独引入模块。
# 1. generator生成器
我们不建议使用generator初始化express项目,因为使用它创建项目的时候,会安装很多不常用的功能,如果我们的项目中仅用到其中很小一部分功能时,就太浪费资源了。
如果你一定要安装使用generator初始化项目,那么可以这样做:
npm install express-generator -g
express和express-generator的区别:
- express只是一个框架,是在被引入项目中使用的,而非工具。
- express-generator则是一个工具,此工具的作用是生成express项目。
安装成功之后,可以通过express命令调用该工具,需要明确的是,在没有安装express-generator前,express命令是没法正确执行的
# 创建express项目,该项目的文件夹名字叫做myapp
express myapp
# 切换目录
cd myapp
# 安装依赖
npm install
# 运行项目
npm run start
2
3
4
5
6
7
8
9
10
11
12
浏览器访问 http://localhost:3000 就可以看到项目了
刚才说了,上述方法消耗资源太大,所以一般我们开发express项目时,都是自己一步一步单独配置的。
# 2. Express框架
创建一个名字叫做myapp的空白文件夹,命令行进入到myapp。
cd myapp
初始化项目,通过 init 命令创建 package.json 文件。
cnpm init -y
然后执行下面的命令,安装express模块。
cnpm i express -S
在myapp文件夹下,创建app.js页面,里面编写搭建服务器的代码。
var express = require('express');
var app = express();
// 路由
app.get('/', function (req, res) {
res.send('hello');
})
// 开启服务器
var server = app.listen(8080, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
代码编写完毕后,在命令行执行命令,挂起服务。
nodemon app.js
然后打开浏览器,访问 http://localhost:8080 就可以看到项目了
# 3. 静态资源托管 static
NodeJS有一个特点,无论用户访问哪个页面时,都会被app.js文件拦截下来,所以得通过路由来解决该问题。
一个网站中,一定会有很多的图片、css文件、js文件的,如果给每一个文件都单独配置路由,会变得特别麻烦。
所以express中内置了中间件 express.static 允许设置静态文件,只需要将文件夹配置出来,然后用户每次访问页面时,都先在该文件夹中查找匹配,如果没有匹配的文件,才向下在app.js的路由中查找。
比如项目根目录下有一个public文件夹,该文件夹下有1.jpg文件。
project
node_modules
public
1.jpg
app.js
2
3
4
5
app.use是配置中间件的方法,express.static是中间件。
app.use(express.static('public'));
运行程序后,浏览器访问 http://localhost/1.jpg,就能够看到图片了。
- 访问文件时不需要写public
- 允许设置多个静态目录
app.use(express.static('abc'));
app.use(express.static('xyz'));
2
中间件的概念是什么?
当用户每次访问一个页面时,先被该中间件处理一下,然后在正常的向下执行程序。
中间件就是一个函数,中间件具体的作用,取决于中间件里面的代码是怎么写的,比如刚才的 static 中间件,起到的作用就是每次用户访问页面时,先在所配置的文件夹中查找文件,如果没有找到文件,则找 app.js 中的路由。
- 使用:app.use
- 请求体解析:使用bodyParse中间件,给req添加body对象
- 静态资源中间件:app.use("/static", express.static(__dirname + '/public')),自带
- cookie处理:cookie-parser中间件,给req和res添加cookie对象
- 路由中间件:自带,app.use('/user', (req, res, next) => {})
- 以/user开头
- next表示执行下一个中间件
const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')
//2、中间件
app.use("/static", express.static(__dirname + '/public'));
// 解析 application/json
app.use(bodyParser.json());
// 解析 application/x-www-form-urlencoded
app.use(bodyParser.urlencoded());
app.use(cookieParser());
// ⽐如访问/user 开头的⻚⾯需要鉴权
app.use('/user', (req, res, next) => {
console.log('todo 访问/user⻚⾯需要验证身份')
if(req.query.id){
next() //继续执行下一个中间件
}else{
next(new Error('鉴权失败'))
}
})
//错误处理中间件
app.use('/user', (err, req, res, next) => {
console.log('todo 访问/user⻚⾯需要验证身份')
res.send(err.message)
})
//1、路由
app.get('/', (req, res) => {
res.send('⾸⻚')
})
app.get('/user/:userId', (req, res) => {
console.log(req.query, req.params)
res.send('⽤户⻚')
})
app.post('/user', (req, res) => {
console.log(req.body)
console.log('cookie: ', req.headers.cookie, req.cookies)
res.cookie('age', '5')
res.send('⽤户⻚post\n')
})
app.listen(3001, () => {
console.log(`express-server started at 3001`)
})
app.on('error', function(err){
consolg.log('error: ', err)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- 中间件执行流程和原理
- 洋葱模型
- 异步模式:即使下一个中间件有异步处理,当前中间件不会等待下一个中间件处理完成
# 4. 配置路由
# get和post路由
app.get('/a', function (req, res) {
res.send('这是get时的a');
})
app.get('/b', function (req, res) {
res.send('这是get时的b');
})
app.post('/a', function (req, res) {
res.send('这是post时的a');
})
2
3
4
5
6
7
8
9
10
11
路由配置完毕后,记得通过浏览器访问一下,看看效果。
# 子路由
使用:require('express').Router()
app.use('/api', apiHandler) //api开头前缀
//apiHandler.js
const router = require('express').Router()
router.post('/news', (req, res) => {
res.send(req.path) //这里的path为/news
})
router.get('/list', (req, res) => {
res.send(req.path)
})
module.exports = router
2
3
4
5
6
7
8
9
10
11
# 通配符
支持通配符匹配
app.get('/b/*.html', function (req, res) {
res.send('b目录下,任意名称的html文件');
})
app.get('/a*c.html', function (req, res) {
res.send('符合a开头c结尾的名称的html文件');
})
2
3
4
5
6
7
# 正则
支持正则表达式匹配
app.get(/^\/(a|b)\.html$/, function (req, res) {
res.end("根目录下的a.html或b.html");
})
2
3
# next
猜一猜下面的程序输出什么?
app.get('/a', function (req, res) {
console.log("你好");
})
app.get('/a', function (req, res) {
console.log("中文");
})
2
3
4
5
6
7
浏览器访问/a时,按照代码的书写顺序,仅执行了一个。
如果想让第二段代码也能够执行,需要调用next方法。
app.get('/a', function (req, res, next) {
console.log("1");
next();
console.log("3");
})
app.get('/a', function (req, res) {
console.log("2");
})
2
3
4
5
6
7
8
9
# send与sendFile
关于输出
app.get('/a', function (req, res, next) {
// express框架写法
//res.setHeader("Content-Type", "text/html");
//res.setHeader('Access-Control-Allow-Origin','*');
res.send("中文不会出现乱码,但只能发送一次");
//res.json({err:0, text:'输出json'})
//res.sendFile(__dirname+"/form.html"); // 将这个文件的内容作为响应到页面上
//res.redirect('/b'); // 页面跳转
})
2
3
4
5
6
7
8
9
api接口规范中,返回的内容应该是json,要有属性描述状态码。
状态码属性叫什么名都可以,通常叫 err、code、state、status、type 等具有语义的名字。
// 示例
res.json({
err : 0,
text : '成功啦'
})
2
3
4
5
# get数据(重点)
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.sendFile( __dirname+"/form.html" );
})
app.get('/chk', function (req, res) {
console.log(req.query);
})
app.listen(80)
2
3
4
5
6
7
8
9
10
11
12
# post数据(重点)
var express = require('express');
var app = express();
// cnpm i body-parser -S
var bodyParser = require('body-parser');
app.use(bodyParser());
app.get('/', function (req, res) {
res.sendFile( __dirname+"/form.html" );
})
app.post('/chk', function (req, res) {
console.log(req.body)
})
app.listen(80)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# params数据
app.get('/news/:id', (req, res)=>{
console.log( req.params );
res.send('结束');
})
2
3
4
# 5. 错误处理
错误处理是在⼀个特殊签名的中间件中完成的,⼀般被添加到链的最后
- 可以通过next传递错误
- 可以是代码运⾏错误
- 使用最后的中间件捕获
// 可以通过next传递错误
app.get('/test', (req, res, next) => {
console.log('test')
next(new Error('500 can only greet "world"'))
// res.send('end')
})
// 可以是代码运⾏错误
app.use((req, res) => {
if (req.query.greet !== 'world') {
throw new Error('can only greet "world"')
}
res.status(200)
res.send(`Hello ${req.query.greet} from Express`)
})
// 会被最后的中间件捕获,4个参数即可不必放置在最后
app.use((err, req, res, next) => {
if (!err) {
next()
return
}
console.log('Error handler:', err.message)
res.status(500)
res.send('Uh-oh: ' + err.message)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 6. 模版引擎EJS
现在讲究前后端彻底分离,所以不推荐使用模版EJS或Jade
EJS 是一个 JavaScript 模板库,用来从 JSON 数据中生成 HTML 字符串。
# 基础用法
目录结构
node_modules/
views/
index.ejs
app.js
2
3
4
views/index.ejs
<p>Hi<%=name%></p>
app.js
const express = require('express');
const app = express();
// 模版引擎设置为ejs
// npm install ejs
app.set('view engine', 'ejs'); // 规定必须要这么写
// 路由
app.get('/',function (req, res) {
// 渲染
res.render('index',{
'name' : '张三'
})
});
app.listen(80);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当用户在浏览器中访问根目录时,渲染的是 views/index.ejs和{name:'张三'}替换合并后的内容。
# 语法
if 语句
<%
if ( 条件 ){
}else{
}
%>
2
3
4
5
6
7
关于循环
<ul>
<%
for ( var i=0; i<arr.length; i++ ) {
%>
<li>
<%=arr[i]%>
</li>
<%
}
%>
<%
for( var i in arr ){
%>
<%-'<li> -表示转义 '+arr[i]+'</li>'%>
<li><%=arr[i]%></li>
<%
}
%>
</ul>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%-以html的形式输出%>
include 引入其他模版
b.ejs
<%= data %>
a.ejs
1
<% include b.ejs %>
2
2
3
在渲染 a.ejs 时,在其内容1与2之间,会把 b.ejs 的内容渲染出来。
不能写成<%= include%>,但可以写成<%- include%>