1. Express 入门
1.1 什么是 Express
Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供一系列强大的特性,帮助你快速创建各种 Web 和移动设备应用。 Express 中的路由和中间件为我们的开发带来极大便利。
简单来说, Express 就是一个第三方模块,专门用来创建 HTTP服务。
1.2 相关网站
- 官网: http://expressjs.com/
- 中文网:https://www.expressjs.com.cn/
- Github: https://github.com/expressjs/express
1.3 安装
npm install express
1.4 创建http服务
//导入 express 模块
const express = require('express');
//创建express实例
const app = express();
// 监听端口
app.listen(3000, function () {
console.log('http server is running on port 300');
});
1.5 使用简单路由
// get 方式, url中的 pathname 是 /
app.get('/', (req, res) => {
res.send(`<h1>首页</h1><a href="/login">登录</a>`);
});
// get 方式,url 中的 pathname 是 /login
app.get('/login', (req, res) => {
res.sendFile(path.join(__dirname, 'login.html'));
});
// post 方式,url 中的 pathname 是 /login
app.post('/login', (req,res) => {
res.end('submit success');
});
1.6 托管静态文件(中间件的使用)
将静态资源文件所在的目录作为参数传递给 express.static
中间件就可以提供静态资源文件的访问了。例如,假设在 public
目录放置了图片、CSS 和 JavaScript 文件,你就可以:
app.use(express.static('public'));
如果你的静态资源存放在多个目录下面,你可以多次调用 express.static
中间件:
访问静态资源文件时,express.static
中间件会根据目录添加的顺序查找所需的文件。
app.use(express.static('public'));
app.use(express.static('files'));
如果你希望所有通过 express.static
访问的文件都存放在一个“虚拟(virtual)”目录(即目录根本不存在)下面,可以通过为静态资源目录指定一个挂载路径的方式来实现,如下所示:
app.use('/static', express.static('public'));
2 路由
路由确定了应用程序如何响应客户端对特定端点的请求。
2.1 路由方法
一个路由的组成有 请求方法
, 路径
和 回调函数
组成,express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:
app.路由方法(path,callback)
express 定义了如下路由方法:
方法名 | 描述 |
---|---|
app.get() | get 请求的路由 |
app.post() | post 请求的路由 |
app.put() | put 请求的路由 |
app.head() | head 请求的路由 |
app.delete() | delete 请求的路由 |
app.options() | options 请求的路由 |
app.trace() | trace 请求的路由 |
app.connect() | connect 请求的路由 |
app.all() | 所有请求方法的路由 |
app.all()
是一个特殊的路由方法,没有任何 HTTP 方法与其对应,它的作用是对于一个路径上的所有请求加载中间件。
2.3 路由路径匹配
1. URL 中的 pathname 需要与路由方法进行匹配,匹配成功才能执行对应的回调函数
2. URL 中只有 pathname 参与匹配,查询字符串不是路径的一部分
使用字符串精准匹配
// 匹配根路径的请求
app.get('/', function (req, res) {
res.send('root');
});
// 匹配 /about 路径的请求
app.get('/about', function (req, res) {
res.send('about');
});
// 匹配 /random.text 路径的请求
app.get('/random.text', function (req, res) {
res.send('random.text');
});
使用字符串模糊
// 匹配 acd 和 abcd
app.get('/ab?cd', function(req, res) {
res.send('ab?cd');
});
// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', function(req, res) {
res.send('ab+cd');
});
// 匹配 abcd、abxcd、abRABDOMcd、ab123cd等
app.get('/ab*cd', function(req, res) {
res.send('ab*cd');
});
// 匹配 /abe 和 /abcde
app.get('/ab(cd)?e', function(req, res) {
res.send('ab(cd)?e');
});
字符
?
、+
和()
是正则表达式的子集。
*
表示任意数量的任意字符。
-
和.
在基于字符串的路径中按照字面值解释。
使用正则表达式匹配
// 匹配任何路径中含有 a 的路径:
app.get(/a/, function(req, res) {
res.send('/a/');
});
// 匹配 butterfly、dragonfly,不匹配 butterflyman、dragonfly man等
app.get(/.*fly$/, function(req, res) {
res.send('/.*fly$/');
});
路径中带有参数
// /articles/12313
app.get('/articles/:id', function(req, res){
res.send(req.params.id)
})
2.4 路由回调函数
使用一个回调函数处理路由:
app.get('/example/a', function (req, res) {
res.send('Hello from A!');
});
使用多个回调函数处理路由(记得指定 next
参数):
app.get('/example/b', function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
使用回调函数数组处理路由:
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
var cb2 = function (req, res) {
res.send('Hello from C!');
}
app.get('/example/c', [cb0, cb1, cb2]);
混合使用函数和函数数组处理路由:
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from D!');
});
2.5 app.route()
可使用 app.route()
创建路由路径的链式路由句柄,由于路径在一个地方指定,这样做有助于创建模块化的路由,而且减少了代码冗余和拼写错误。
app.route('/book')
.get(function(req, res) {
res.send('Get a random book');
})
.post(function(req, res) {
res.send('Add a book');
})
.put(function(req, res) {
res.send('Update the book');
});
2.6 express.Router
温馨提示: 请学习完中间件之后再来研究本小节内容!
可使用 express.Router
类创建模块化、可挂载的路由句柄。Router
实例是一个完整的中间件和路由系统,因此常称其为一个 “mini-app”。
下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上。
在 app 目录下创建名为 birds.js
的文件,内容如下:
var express = require('express');
var router = express.Router();
// /birds
router.get('/', function(req, res) {
res.send('Birds home page');
});
// /birds/about
router.get('/about', function(req, res) {
res.send('About birds');
});
module.exports = router;
然后在应用中加载路由模块:
var birds = require('./birds');
...
app.use('/birds', birds);
3 请求和响应
3.1 请求对象
request 对象是路由回调函数中的第一个参数,代表了用户发送给服务器的请求信息,通过 request 对象可以读取用户发送的请求包括 URL 地址中的查询字符串中的参数,和 post 请求的请求体中的参数。
获取客户端 IP
request.ip
获取请求头
request.get('请求头key');
查询字符串信息
request.query; // 获取一个对象
路径中的参数信息
// 路径中的参数信息代替查询字符串 匹配 /news/20342323/a12.shtml
app.get('/news/:date/:id.shtml', (request, response) => {
request.params; // 得到一个对象,有 date 和 id 属性
})
获取请求体
request.body 必须经过 body-parser 中间件的处理才能获取到请求体数据解析成的对象,否则只能得到 undefined, body-parser 需要 npm 安装
const bodyParser = require('body-parser');
// 创建 express 应用
const app = express();
// 使用 body-parser 解析请求体内容
// 请求时数据类型是 application/x-www-form-urlencoded,form表单提交的请求体就是这种类型
app.use(bodyParser.urlencoded({ extended: false }));
// 请求体数据类型是 application/json,需要使用下面的处理方式
app.use(bodyParser.json())
app.post('/login', (request, response) => {
request.body; // 请求体对象
});
3.2 响应对象
response 对象是路由回调函数中的第二个参数,代表了服务器发送给用户的响应信息,通过 response 对象可以设置响应报文中的各个内容,包括响应头和响应体。
设置响应状态码
response.status(404);
设置响应头
response.set('响应头key', '值');
设置响应体
response.end()
response.send() 比起 end() 可以自动追加 Content-type 响应头
response.sendFile(文件地址) 将文件中的内容读取作为响应体
response.download(文件地址) 将文件下载
response.json() 将对象转为json字符串,进行响应
response.jsonp() 将对象转为jsonp调用形式,进行响应
response.render() 渲染模板
重定向
response.redirect() 重定向
响应对像的方法支持链式调用
response.status(250).set('响应头Key', '响应体Value').send('响应体内容');
4 中间件
Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。
中间件(Middleware) 是一个函数,它可以接收参数 请求对象(req)), 响应对象( res), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next
的变量。
中间件的功能包括:
① 执行任何代码。
② 修改请求和响应对象。
③ 结束响应 。
④ 调用堆栈中的下一个中间件。
如果当前中间件没有结束响应,则必须调用 next()
方法将控制权交给下一个中间件,否则请求就会挂起。
4.1 应用级中间件
应用级中间件绑定到 app 对象, 使用 app.use()
方法或者路由方法,如app.get()
、app.post()
等,我们之前学习的路由方法的回调函数本质上也是中间件。
var app = express();
// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 设置多高回调函数
app.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
作为中间件系统的路由句柄,使得为路径定义多个路由成为可能。在下面的例子中,为指向 /user/:id
的 GET 请求定义了两个路由。第二个路由虽然不会带来任何问题,但却永远不会被调用,因为第一个路由使用 res.send()
已结束了响应。
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
res.send('User Info');
});
// 处理 /user/:id, 打印出用户 id
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id);
});
如果需要在中间件栈中跳过剩余中间件,调用 next('route')
方法将控制权交给下一个路由。
注意: next('route')
只对使用 app
的路由方法或 router
的路由方法加载的中间件有效。
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制权交给栈中下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染常规页面
res.render('regular');
});
// 处理 /user/:id, 渲染一个特殊页面
app.get('/user/:id', function (req, res, next) {
res.render('special');
});
4.2 错误处理中间件
错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要
next
对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理
错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其写法如下: (err, req, res, next)
。
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
4.3 路由级中间件
路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()
的返回值,路由级使用 router.use()
或 router
的路由方法,如router.post()
、router.get()
等。使用路由级中间件可以实现路由模块化,如下:
定义路由模块,脚本文件 routes/user.js
const express = require('express');
const router = express.Router();
// 匹配路径 /user
router.get('/', function(req, res, next) {
res.send()
});
// 匹配路径 GET: /user/login
router.get('/login', function(req, res, next) {
res.send()
});
// 匹配路径 POST: /user/login
router.post('/login', function(req, res, next) {
res.send()
});
// 匹配路径 /user/logout
router.get('/logout', function(req, res, next) {
res.send()
});
// 将 router 对象对外暴露
module.exports = router;
在应用入口文件中导入路由模块:
const express = require('express');
const userRouter = require('./routes/user.js')
const app = express();
// 将路由挂载至应用,并指定路径 /user
app.use('/user', userRouter);
4.4 内置中间件
- express.static 静态资源服务,如 html文件、图片 等
- express.json parses incoming requests with JSON payloads. NOTE: Available with Express 4.16.0+
- express.urlencoded parses incoming requests with URL-encoded payloads. NOTE: Available with Express 4.16.0+
4.5 第三方中间件
常用的第三方中间件 列表 http://expressjs.com/en/resources/middleware.html
- body-parser 解析HTTP 请求体
- compression 压缩HTTP响应
- connect-rid 生成唯一请求ID
- cookie-parser 解析cookie header 并 填充req.cookies
- cookie-session 创建基于cookie的session
- cors 启用跨来源资源共享(CORS)通过各种选项。
- csurf CSRF保护
- errorhandler 错误处理与调试
- method-override 覆盖HTTP请求方法
- morgan HTTP请求
- multer 处理 multi-part form data
- response-time 记录HTTP响应时间
- serve-favicon 提供网站图标(favicon)
- serve-index 显示目录列表
- serve-static 静态文件服务
- session 建立基于服务器的会话(仅开发)
- timeout 设置HTTP请求处理的超时周期。
- vhost 创建虚拟主机
5 模板引擎
3.1 模板引擎设置
express 中常见的模板引擎有两个,ejs 和 jade,这里以 ejs 为例:
//1. 设置 express 所使用的模板引擎 会根据这里的设置自动引入模板引擎,必须再写 require()
app.set('view engine', 'ejs');
//2. 设置模板文件的存放目录
app.set('views', path.join(__dirname, 'pages'));
注意: 默认模板文件的扩展名与模板引擎的名字一致,如
ejs
模板引擎中模板文件的扩展名是.ejs
。
3.2 渲染模板页面
响应对象的 render()
方法专门用于渲染模板页面,需要指定模板页面地址和在页面上渲染的数据。
app.get('/', function (req, res) {
// 会在模板文件的存放目录中查找 index.ejs 文件
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
3.3 修改模板文件扩展名
const ejs = require('ejs');
//1. 更改模板引擎名字为 html
app.engine('html', ejs.renderFile);
//2. 设置 express 所使用的模板引擎
app.set('view engine', 'html');
//3. 设置模板文件的存放目录
app.set('views', path.join(__dirname, 'pages'));
5.4 自定义 Express 模板引擎
通过 app.engine(ext, callback)
方法即可创建一个你自己的模板引擎。其中,ext
指的是文件扩展名、callback
是模板引擎的主函数,接受文件路径、参数对象和回调函数作为其参数。
var fs = require('fs'); // 此模板引擎依赖 fs 模块
app.engine('ntl', function (filePath, options, callback) { // 定义模板引擎
fs.readFile(filePath, function (err, content) {
if (err) return callback(new Error(err));
// 这是一个功能极其简单的模板引擎
var rendered = content.toString().replace('#title#', '<title>'+ options.title +'</title>')
.replace('#message#', '<h1>'+ options.message +'</h1>');
return callback(null, rendered);
})
});
app.set('views', './views'); // 指定视图所在的位置
app.set('view engine', 'ntl'); // 注册模板引擎
//渲染
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
})
6 错误处理
6.1 错误处理中间件的定义和使用
定义错误处理中间件和定义其他中间件一样,除了需要 4 个参数,而不是 3 个,其格式如下 (err, req, res, next)
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
一般情况下,在其他 app.use() 和路由调用后,最后定义错误处理中间件
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
// 各种路由
// ...
app.use(function(err, req, res, next) {
// 业务逻辑
});
中间件返回的响应是随意的,可以响应一个 HTML 错误页面、一句简单的话、一个 JSON 字符串,或者其他任何您想要的东西。
6.2 多个错误处理中间件
为了便于组织(更高级的框架),您可能会像定义常规中间件一样,定义多个错误处理中间件。比如您想为使用 XHR 的请求定义一个,还想为没有使用的定义一个
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
//logErrors 将请求和错误信息写入标准错误输出、日志或类似服务
function logErrors(err, req, res, next) {
console.error(err.stack);
next(err);
}
//clientErrorHandler 的定义如下(注意这里将错误直接传给了 next
function clientErrorHandler(err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something blew up!' });
} else {
next(err);
}
}
//errorHandler 能捕获所有错误
function errorHandler(err, req, res, next) {
res.status(500);
res.render('error', { error: err });
}
6.3 默认错误处理
Express 内置了一个错误处理句柄,它可以捕获应用中可能出现的任意错误。这个缺省的错误处理中间件将被添加到中间件堆栈的底部。
如果你向 next() 传递了一个 error ,而你并没有在错误处理句柄中处理这个 error,Express 内置的缺省错误处理句柄就是最后兜底的。最后错误将被连同堆栈追踪信息一同反馈到客户端。堆栈追踪信息并不会在生产环境中反馈到客户端。
6.4 404页面定制
app.use((req, res, next) => {
console.log('执行否');
next(new Error('404'));
});
app.use((err, req, res, next) => {
if (err.message == 404) {
res.render('404');
} else {
next(err);
}
})
7 项目生成器和调试
7.1 项目生成器
安装
npm install -g express-generator
使用生成器生成项目骨架
express 项目名称
目录结构
├── app.js # 入口文件
├── bin # 命令目录
│ └── www
├── package.json
├── public # 静态资源目录
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes # 路由目录
│ ├── index.js
│ └── users.js
└── views # 模板目录
├── error.jade
├── index.jade
└── layout.jade
7.2 调试
Express 内部使用 debug 模块记录路由匹配、使用到的中间件、应用模式以及请求-响应循环。
在命令中运行如下命令,开启 debug 模式:
set DEBUG=myapp & npm start # windows平台
DEBUG=myapp & npm start # linux max 平台