安装
创建一个目录,然后进入此目录并将其作为当前工作目录。然后通过npm init命令为你的应用创建一个package.json文件。接下来安装Express并将其保存到依赖列表中。
mkdir myapp
cd myapp
npm init
npm install express --save
Express应用生成器
通过应用生成器工具express可以快速创建一个应用的骨架。
通过如下命令安装:
$ npm install express-generator -g
-h选项可以列出所有可用的命令行选项:
$ express -h
Usage: express [options] [dir]
Options:
--version output the version number
-e, --ejs add ejs engine support
--pug add pug engine support
--hbs add handlebars engine support
-H, --hogan add hogan.js engine support
-v, --view <engine> add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
--no-view use static html instead of view engine
-c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
--git add .gitignore
-f, --force force on non-empty directory
-h, --help output usage information
例如,下面的示例就是在当前工作目录下创建一个命名为myapp的应用。
$ express myapp
create : myapp
create : myapp/package.json
create : myapp/app.js
create : myapp/public
create : myapp/public/javascripts
create : myapp/public/images
create : myapp/routes
create : myapp/routes/index.js
create : myapp/routes/users.js
create : myapp/public/stylesheets
create : myapp/public/stylesheets/style.css
create : myapp/views
create : myapp/views/index.jade
create : myapp/views/layout.jade
create : myapp/views/error.jade
create : myapp/bin
create : myapp/bin/www
然后安装所有依赖包:
$ cd myapp
$ npm install
启动这个应用(MacOS或Linux平台):
$ DEBUG=myapp npm start
Windows平台使用如下命令:
> set DEBUG=myapp & npm start
然后在浏览器中打开http://localhost:3000/网址就可以看到这个应用了。
通过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 directories, 9 files
路由
路由是指如何定义应用的端点以及如何响应客户端的请求。
路由是由一个URI、HTTP请求和若干个句柄组成,它的结构如下:app.METHOD(path, [callback...], callback),app是express对象的一个实例,METHOD是一个HTTP请求方法,path是服务器上的路径,callback是当路由匹配时要执行的函数。
下面是一个基本的路由示例:
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.send('hello world');
});
路由方法
路由方法源于HTTP请求方法,和express实例相关联。
下面这个例子展示了为应用跟路径定义的GET和POST请求:
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
Express定义了如下和HTTP请求对应的路由方法:get、post、put、head、delete、options、trace、copy、lock、mkcol、move、purge、propfind、proppatch、unlock、report、mkactivity、checkout、merge、m-search、notify、subscribe、unsubscribe、patch、search、和connect。
有些路由方法名不是合规的 JavaScript 变量名,此时使用括号记法,比如: app['m-search']('/', function() {})
app.all()是一个特殊的路由方法,没有任何HTTP方法与其对应,它的作用是对于一个路径上的所有请求加载中间件。
在下面的例子中,来自“/secret”的请求,不管使用GET、POST、PUT、DELETE或其他任何http模块支持的HTTP请求,句柄都会得到执行。
app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...');
next(); // pass control to the next handler
});
路由路径
路由路径和请求方法一起定义了请求的端点,它可以是字符串、字符串模式或者正则表达式。
查询字符串不是路由路径的一部分。
// 使用字符串的路由路径
// 匹配根路径的请求
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$/');
});
路由句柄
可以为请求处理提供多个回调函数,其行为类似中间件。唯一的区别是这些回调函数有可能调用next('route')方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。
路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合。
// 使用一个回调函数处理路由
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!');
});
响应方法
下表中响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。
| 方法 | 描述 |
|---|---|
res.download() |
提示下载文件。 |
res.end() |
终结响应处理流程。 |
res.json() |
发送一个JSON格式的响应。 |
res.jsonp() |
发送一个支持JSONP的JSON格式的响应。 |
res.redirect() |
重定向请求。 |
res.render() |
渲染视图模板。 |
res.send() |
发送各种类型的响应。 |
res.sendFile |
以八位字节流的形式发送文件。 |
res.sendStatus() |
设置响应状态代码,并将其以字符串形式作为响应体的一部分发送。 |
app.route()
可使用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');
});
express.Router
可使用express.Router类创建模块化、可挂载的路由句柄。Router实例是一个完整的中间件和路由系统,因此常称其为一个mini-app。
下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上。
在app目录下创建名为birds.js的文件,内容如下:
var express = require('express');
var router = express.Router();
// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
res.send('About birds');
});
module.exports = router;
然后在应用中加载路由模块。
var birds = require('./birds');
app.use('/birds', birds);
应用即可处理发自/birds和/birds/about的请求,并且调用为该路由指定的timeLog中间件。
使用中间件
Express是一个自身功能极简,完全是由路由和中间件构成一个的web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。
中间件(Middleware)是一个函数,它可以访问请求对象(request object(req)), 响应对象(response object(res)),和web应用中处于请求—响应循环流程中的中间件,一般被命名为next的变量。
中间件的功能包括:
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件
如果当前中间件没有终结请求—响应循环,则必须调用next()方法将控制权交给下一个中间件,否则请求就会挂起。
Express应用可使用如下几种中间件:
- 应用级中间件
- 路由级中间件
- 错误处理中间件
- 内置中间件
- 第三方中间件
使用可选则挂载路径,可在应用级别或路由级别装载中间件。另外,还可以同时装在一系列中间件函数,从而在一个挂载点上创建一个子中间件栈。
应用级中间件
应用级中间件绑定到app对象使用app.use()和app.METHOD(),其中,METHOD是需要处理的HTTP请求的方法,例如GET,PUT,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();
});
// 路由和句柄函数(中间件系统),处理指向/user/:id的GET请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
下面这个例子展示了在一个挂载点装载一组中间件。
// 一个中间件栈,对任何指向/user/:id的HTTP请求打印出相关信息
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请求定义了两个路由。第二个路由虽然不会带来任何问题,但却永远不会被调用,因为第一个路由已经终止了请求—响应循环。
// 一个中间件栈,处理指向/user/:id的GET请求
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
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.VERB()或router.VERB()加载的中间件有效。
// 一个中间件栈,处理指向/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');
});
路由级中间件
路由级中间件和应用级中间件一样,只是它绑定的对象为express.Router()。
var router = express.Router();
路由级使用router.use()或router.VERB()加载。
上述在应用级创建的中间件系统,可通过如下代码改写为路由级。
var app = express();
var router = express.Router();
// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 一个中间件栈,显示任何指向/user/:id的HTTP请求的信息
router.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请求
router.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, 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
// 将路由挂载至应用
app.use('/', router);
错误处理中间件
错误处理中间件有4个参数,定义错误处理中间件时必须使用这4个参数。即使不需要next对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
内置中间件
从4.x版本开始,Express已经不再依赖Connect了。除了express.static,Express以前内置的中间件现在已经全部单独作为模块安装使用了。
express.static(root, [options])
express.static是Express唯一内置的中间件。它基于serve-static,负责在Express应用中提供管静态资源。
参数root指提供静态资源的根目录。可选的options参数拥有如下属性。
| 属性 | 描述 | 类型 | 缺省值 |
|---|---|---|---|
dotfiles |
是否对外输出文件名以点(.)开头的文件。可选值为 allow、deny和ignore
|
String |
ignore |
etag |
是否启用etag生成 |
Boolean |
true |
extensions |
设置文件扩展名备份选项 | Array |
[] |
index |
发送目录索引文件,设置为false禁用目录索引。 |
Mixed |
index.html |
lastModified |
设置Last-Modified头为文件在操作系统上的最后修改日期。可能值为true或false。 |
Boolean |
true |
maxAge |
以毫秒或者其字符串格式设置Cache-Control头的max-age属性。 |
Number |
0 |
redirect |
当路径为目录时,重定向至/。 |
Boolean |
true |
setHeaders |
设置HTTP头以提供文件的函数。 | Function |
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now());
}
}
app.use(express.static('public', options));
每个应用可有多个静态目录。
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));
第三方中间件
通过使用第三方中间件从而为Express应用增加更多功能。
安装所需功能的node模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。
// 安装并加载解析cookie的中间件:cookie-parser
$ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
// 加载用于解析cookie的中间件
app.use(cookieParser());
在Express中使用模板引擎
需要在应用中进行如下设置才能让Express渲染模板文件:
-
views:放模板文件的目录,比如:app.set('views', './views') -
view engine:模板引擎,比如:app.set('view engine', 'jade')
然后安装相应的模板引擎npm软件包。
$ npm install jade --save
和Express兼容的模板引擎,比如Jade,通过res.render()调用其导出方法__express(filePath, options, callback)渲染模板。
有一些模板引擎不遵循这种约定,Consolidate.js能将Node中所有流行的模板引擎映射为这种约定,这样就可以和Express无缝衔接。
一旦view engine设置成功,就不需要显式指定引擎,或者在应用中加载模板引擎模块,Express已经在内部加载,如下所示。
app.set('view engine', 'jade');
在views目录下生成名为index.jade的Jade模板文件,内容如下:
html
head
title!= title
body
h1!= message
然后创建一个路由渲染index.jade文件。如果没有设置view engine,您需要指明视图文件的后缀,否则就会遗漏它。
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
此时向主页发送请求,index.jade会被渲染为HTML。
错误处理
定义错误处理中间件和定义其他中间件一样,除了需要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字符串,或者其他任何东西。
为了便于组织(更高级的框架),可能会像定义常规中间件一样,定义多个错误处理中间件。比如您想为使用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 });
}
如果向next()传入参数(除了'route'字符串),Express会认为当前请求有错误的输出,因此跳过后续其他非错误处理和路由/中间件函数。如果需做特殊处理,需要创建新的错误处理路由。
如果路由句柄有多个回调函数,可使用'route'参数跳到下一个路由句柄。比如:
app.get('/a_route_behind_paywall',
function checkIfPaidSubscriber(req, res, next) {
if(!req.user.hasPaid) {
// 继续处理该请求
next('route');
}
}, function getPaidContent(req, res, next) {
PaidContent.find(function(err, doc) {
if(err) return next(err);
res.json(doc);
});
});
在这个例子中,句柄getPaidContent会被跳过,但app中为/a_route_behind_paywall定义的其他句柄则会继续执行。
next()和next(err)类似于Promise.resolve()和Promise.reject()。它们让您可以向Express发信号,告诉它当前句柄执行结束并且处于什么状态。next(err)会跳过后续句柄,除了那些用来处理错误的句柄。
缺省错误处理句柄
Express内置了一个错误处理句柄,它可以捕获应用中可能出现的任意错误。这个缺省的错误处理中间件将被添加到中间件堆栈的底部。
如果你向next()传递了一个error,而你并没有在错误处理句柄中处理这个error,Express内置的缺省错误处理句柄就是最后兜底的。最后错误将被连同堆栈追踪信息一同反馈到客户端。堆栈追踪信息并不会在生产环境中反馈到客户端。
设置环境变量NODE_ENV为“production”就可以让应用运行在生产环境模式下。
如果你已经开始向response输出数据了,这时才调用next()并传递了一个error,比如你在将向客户端输出数据流时遇到一个错误,Express内置的缺省错误处理句柄将帮你关闭连接并告知request请求失败。
因此,当你添加了一个自定义的错误处理句柄后,如果已经向客户端发送包头信息了,你还可以将错误处理交给Express内置的错误处理机制。
function errorHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
res.status(500);
res.render('error', { error: err });
}
