以下内容,基于 Express 4.x 版本
Node.js 的 Express
Express 估计是那种你第一次接触,就会喜欢上用它的框架。因为它真的非常简单,直接。
在当前版本上,一共才这么几个文件:
lib/
├── application.js
├── express.js
├── middleware
│ ├── init.js
│ └── query.js
├── request.js
├── response.js
├── router
│ ├── index.js
│ ├── layer.js
│ └── route.js
├── utils.js
└── view.js
这种程度,说它是一个“框架”可能都有些过了,几乎都是工具性质的实现,只限于 Web 层。
当然,直接了当地实现了 Web 层的基本功能,是得益于 Node.js 本身的 API 中,就提供了 net 和 http 这两层, Express 对 http 的方法包装一下即可。
不过,本身功能简单的东西,在 package.json
中却有好长一串 dependencies 列表。
Hello World
在跑 Express 前,你可能需要初始化一个 npm 项目,然后再使用 npm 安装 Express:
mkdir p
cd p
npm init
npm install express --save
新建一个 app.js
:
const express = require('express');
const app = express();
app.all('/', (req, res) => res.send('hello') );
app.listen(8888);
调试信息是通过环境变量 DEBUG 控制的:
const process = require('process');
process.env['DEBUG'] = 'express:*';
这样就可以在终端看到带颜色的输出了,嗯,是的,带颜色控制字符,vim 中直接跑就 SB 了。
应用 Application
Application 是一个上层统筹的概念,整合“请求-响应”流程。 express()
的调用会返回一个 application ,一个项目中,有多个 app 是没问题的:
const express = require('express');
const app = express();
app.all('/', (req, res) => res.send('hello'));
app.listen(8888);
const app2 = express();
app2.all('/', (req, res) => res.send('hello2'));
app2.listen(8889);
多个 app 的另一个用法,是直接把某个 path 映射到整个 app :
const express = require('express');
const app = express();
app.all('/', (req, res) => {
res.send('ok');
});
const app2 = express();
app2.get('/xx', (req, res, next) => res.send('in app2') )
app.use('/2', app2)
app.listen(8888);
这样,当访问 /2/xx
时,就会看到 in app2
的响应。
前面说了 app 实际上是一个上层调度的角色,在看后面的内容之前,先说一下 Express 的特点,整体上来说,它的结构基本上是“回调函数串行”,无论是 app ,或者 route, handle, middleware这些不同的概念,它们的形式,基本是一致的,就是 (res, req, next) => {}
,串行的流程依赖 next()
的显式调用。
我们把 app 的功能,分成五个部分来说。
路由 - Handler 映射
app.all('/', (req, res, next) => {});
app.get('/', (req, res, next) => {});
app.post('/', (req, res, next) => {});
app.put('/', (req, res, next) => {});
app.delete('/', (req, res, next) => {});
上面的代码就是基本的几个方法,路由的匹配是串行的,可以通过 next()
控制:
const express = require('express');
const app = express();
app.all('/', (req, res, next) => {
res.send('1 ');
console.log('here');
next();
});
app.get('/', (req, res, next) => {
res.send('2 ');
console.log('get');
next();
});
app.listen(8888);
对于上面的代码,因为重复调用 send()
会报错。
同样的功能,也可以使用 app.route()
来实现:
const express = require('express');
const app = express();
app.route('/').all( (req, res, next) => {
console.log('all');
next();
}).get( (req, res, next) => {
res.send('get');
next();
}).all( (req, res, next) => {
console.log('tail');
next();
});
app.listen(8888);
app.route()
也是一种抽象通用逻辑的形式。
还有一个方法是 app.params
,它把“命名参数”的处理单独拆出来了(我个人不理解这玩意儿有什么用):
const express = require('express');
const app = express();
app.route('/:id').all( (req, res, next) => {
console.log('all');
next();
}).get( (req, res, next) => {
res.send('get');
next()
}).all( (req, res, next) => {
console.log('tail');
});
app.route('/').all( (req, res) => {res.send('ok')});
app.param('id', (req, res, next, value) => {
console.log('param', value);
next();
});
app.listen(8888);
app.params
中的对应函数会先行执行,并且,记得显式调用 next()
。
Middleware
其实前面讲了一些方法,要实现 Middleware 功能,只需要 app.all(/.*/, () => {})
就可以了, Express 还专门提供了 app.use()
做通用逻辑的定义:
const express = require('express');
const app = express();
app.all(/.*/, (req, res, next) => {
console.log('reg');
next();
});
app.all('/', (req, res, next) => {
console.log('pre');
next();
});
app.use((req, res, next) => {
console.log('use');
next();
});
app.all('/', (req, res, next) => {
console.log('all');
res.send('/ here');
next();
});
app.use((req, res, next) => {
console.log('use2');
next();
});
app.listen(8888);
注意 next()
的显式调用,同时,注意定义的顺序, use()
和 all()
顺序上是平等的。
Middleware 本身也是 (req, res, next) => {}
这种形式,自然也可以和 app 有对等的机制——接受路由过滤, Express 提供了 Router ,可以单独定义一组逻辑,然后这组逻辑可以跟 Middleware一样使用。
const express = require('express');
const app = express();
const router = express.Router();
app.all('/', (req, res) => {
res.send({a: '123'});
});
router.all('/a', (req, res) => {
res.send('hello');
});
app.use('/route&#