codecamp

Express4入门指南

大纲
  1. 1. 安装和基本示例
  2. 2. 工作原理
  3. 3. 中间件机制
    1. 3.1. 中间件的分类
  4. 4. Router机制
    1. 4.1. 模式匹配和参数
    2. 4.2. 路由回调
    3. 4.3. app.router()
  5. 5. 模板引擎

Express是基于Nodejs以及一系列Nodejs第三方package的一款Web开发框架。

Express经历过2.x,3.x以及最新的4.x版本。Express各个版本的差异还是比较大的。现在2.x版本官方已经不再维护(deprecated),3.x版本虽然可以使用,但是官方建议升级到4.x版本。如果你之前使用的express 3.x版本,官方也有给出迁移到4.x的指南

本文围绕express的Routing和Middleware机制,作一些讨论。主要参考express官网的guide。

安装和基本示例

我们首先从最基本的讲起。首先我们得有一个nodejs项目,


> mkdir test & cd test
> npm init

我一般习惯使用npm init来创建一个nodejs项目。因为它可以提供一个交互式的命令行来生成最基本的package.json文件。

在创建好package.json文件之后,接下来我们就需要安装express了,


> npm install express --save

我们使用--save选项自动更新package.jsondependencies字段。在express安装结束之后,package.json就变成了下面这样了,


{
  "name": "test",
  "version": "1.0.0",
  "description": "kick off express4",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "nodejs"
  ],
  "author": "gejiawen",
  "license": "MIT",
  "dependencies": {
    "express": "^4.13.3"
  }
}

根据你系统的差异或者网络因素,你可能需要使用sudo npm install express --save,或者是sudo cnpm install express --save

使用npm安装包的时候,除了--save选项,我们还可以使用--save-dev。两者在安装完包之后,都可以自动更新package.json文件,不同的是,前者更新的是dependencies字段,后者更新的是devDependencies字段。而dependenciesdevDependencies的区别可以简单的理解成,前者是生产环境所需要的依赖,而后者是开发环境所需要的依赖。

在安装完毕express之后,我们需要新建一个启动文件,就是package.jsonmain字段指定的文件。当然你说我能不能新建一个文件叫别的什么名字,那当然也是没问题的。


> touch index.js

index.js文件的内容如下,


var express = require('express');
var app = express();
app.get('/', function (req, res) {
    res.send('Hello world!');
});
app.listen(3000, function () {
    console.log('app is listening at localhost:3000');
});

最后再启动这个文件,


> node index.js

至此,我们使用express开发的一个最最简单的web程序就完成了。可以我浏览器输入localhost:3000,看看效果吧。

工作原理

经过上面的简单示例,看起来使用express做web真的非常简单。为什么可以做到这么简单呢?这是跟express的设计理念有关系的。

整个express应用可以简单的抽象成一系列的路由和中间件。当然这些路由和中间件之间存在着调用和先后关系。

那么,路由和中间件具体又是指的是什么呢?

简单来说,express中的路由可以看成一种path watcher,比如上面示例中的代码,


app.get('/', function (req, res) {
    res.send('Hello world!');
});

这段代码中,express app将监控/路径,来自客户端所有对/路径的请求,都使用后面的回调函数处理。

而这里的处理请求的回调函数其实就是所谓的中间件。

所以,中间件其实就是处理HTTP请求的回调函数,一般来说它会有3个参数,分别是reqresnext,分别表示请求对象,响应对象以及next回调。next回调的作用是将控制权传递给下一个中间件。

如果一个中间件是专门用于错误处理的,它将会有4个参数,分别是errreqresnext。其中第一个参数表示错误对象。

中间件的基本执行过程是这样的,在中间件内部对req和res对象进行处理加工,执行相关逻辑,然后根据业务决定是否执行next()函数,传递到下一个中间件

除此之外,一般我们建议一个中间件只专注做一件事,也就是说中间件的职责尽量单一,这样利于维护和协调中间件。

中间件机制

下面我们来详细的说一说express的中间件机制。

其实所谓的中间件就是一个函数回调。express中采用use来注册中间件。比如,


var expresss = require('express');
var app = express();
app.use(function (req, res, next) {
    console.log('Time: ', new Date());
    next();
});
// more code...

如上例所示,这个中间件是整个app的第一个中间件,当有请求过来时,它将会被第一个调用,在console中打印出时间信息。执行完毕之后,通过next()将执行权传递给后面的中间件。

此外,我们可以再中间件内容做一些额外的事情,比如对请求路径的判断,


app.use(function(req, res, next) {
    if (request.url === "/") {
        response.writeHead(200, { "Content-Type": "text/plain" });
        response.end("Welcome to the homepage!\n");
    } else {
        next();
    }
});
app.use(function(req, res, next) {
    if (req.url === "/about") {
        res.writeHead(200, { "Content-Type": "text/plain" });
        res.end("about page!\n");
    } else {
        next();
    }
});
app.use(function(req, res, next) {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("404 error!\n");
})

上面的代码表示,

  • 如果请求的是/路径,返回给客户端Welcome to the homepage!。如果是其他路径,就调用下一个中间件。
  • 第二个中间件中,判断路径是否是/about,如果是,将返回给客户端about page!。如果不是,调用下一个中间件。
  • 当执行权传递到第三个中间件时,那么客户端请求的路径不是/也不是/about,此时我们将给出一个404 error!的提示,并同时终止中间件的继续调用。

从上面的示例代码,我们足以管中窥豹,领略一下express中间件机制,以及它们的调用策略。简单来说就是,express按照顺序执行中间件,每个中间件做好自己的任务并决定是否调用下一个中间件

中间件的分类

express的官网上对中间件作了个分类,包含如下几种,

应用级别的中间件其实就是指挂在app上的中间件。通常我们除了可以通过app.use来挂载中间件之外,express还提供了app.METHOD这种方式。这里的METHOD指的是http的方法。比如

  • app.get
  • app.post
  • app.put

除此之外,还有一个特别的动词我们也可以使用,就是app.all(),它表示所有的请求都会通过这个中间件。看下面的示例代码,


app.all("*", function(request, response, next) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    next();
});
app.get("/", function(request, response) {
    response.end("Welcome to the homepage!");
});
app.get("/about", function(request, response) {
    response.end("Welcome to the about page!");
});
app.get("*", function(request, response) {
    response.end("404!");
});

所有的请求都将会执行第一个中间,用户设置http响应的头信息。如果请求的是/或者/about,则执行相应的中间件。这里注意,第二个和第三个中间件都没有调用next(),所以他们执行完毕之后,并不会调用后面的中间件。如果请求的路径不是/或者/about,那么第二个和第三个中间件都将不会被调用,第四个中间件将会被调用。

所以说,express应用中中间件的顺序是很重要的,它将影响中间件的执行顺序。

Router机制

前面我们知道,可以通过app.use或者是app.METHOD('/')来配置路由。

在Express4中,路由成了一个单独的组件,express提供了一个新的对象express.Router,可以通过它来更好的配置路由。我们先来看一个示例,


// birds.js
var express = require('express');
var router = express.Router();
router.use(function (req, res, next) {
    console.log('Time: ', new Date());
    next();
});
router.get('/', function (req, res) {
    res.send('Birds home page');
});
router.get('/about', function (req, res) {
    res.send('Birds about page');
});
module.exports = router;


// index.js
var express = require('express');
var birds = require('./birds');
var app = express();
app.get('/', function (req, res) {
    res.send('app home page');
});
app.use('/birds', birds);
app.listen(3000, function () {
    console.log('server starting...');
});

通过这个简单的例子,我们可以独立封装一组路由成为一个路由中间件,在主文件中可以根据需要挂在到不同的路径上,如app.use('/birds', birds)。这样一来,路由中间件中对路由的配置最终会被解析成如下,

  • /birds/
  • /birds/about/

我们使用express进行web开发的时候,一般也是推荐建立一个routes文件夹,将所有页面的路径配置都抽象成一个路由中间件,然后在主文件中挂载。

模式匹配和参数

使用进行路由配置的时候,我们不仅仅可以使用字符常量,还可以使用字符串的模式匹配,如下,


// will match acd and abcd
app.get('/ab?cd', function(req, res) {
    res.send('ab?cd');
});
// will match abcd, abbcd, abbbcd, and so on
app.get('/ab+cd', function(req, res) {
    res.send('ab+cd');
});
// will match abcd, abxcd, abRABDOMcd, ab123cd, and so on
app.get('/ab*cd', function(req, res) {
    res.send('ab*cd');
});
// will match /abe and /abcde
app.get('/ab(cd)?e', function(req, res) {
    res.send('ab(cd)?e');
});
// will match anything with an a in the route name:
app.get(/a/, function(req, res) {
    res.send('/a/');
});
// will match butterfly, dragonfly; but not butterflyman, dragonfly man, and so on
app.get(/.*fly$/, function(req, res) {
    res.send('/.*fly$/');
});

如你所见,路由的模式匹配其实很简单。你可以使用正则,让你的路由回调更加随心所欲的匹配不同的路径。

路由参数的意思就是你可以在路由配置时定义好路径中带有的参数,比如


app.get('/book/:bookId', function (req, res, next) {
    console.log(req.params);
});

这样你就可以在回调中,处理req中的params对象。

路由回调

有时候我们可能会有这样一个需求,我需要在路由回调中进行一些预处理。比如下面这个例子


var express = require('express');
var app = express();
app.get('/user/:id', function (req, res, next) {
    if (req.params.id === 0) {
        next('route');
    } else {
        next();
    }
}, function (req, res, next) {
    res.send('regular');
});
app.get('/user/:id', function (req, res, next) {
    res.send('special');
});
app.listen(3000);

上面的示例代码,有两点需要注意,

1.我们可以在路由配置的回调中,添加多个回调函数。如果有多个回调,我们可以有两种形式,如下,


app.get('/', function() {}, function() {});
app.get('/', [callback1, callback2, callback3]);

2.同一个路由配置的多个回调函数在执行时可以被跳过。

上面代码中的next('route')表示跳过当前路由中间件中剩下的路由回调,执行下一个中间件。而next()表示执行当前中间件的下一个路由回调。所以,当我请求/user/100时,返回的是regular。当我请求/user/0时,返回的是special。

app.router()

在express4中我们可以使用app.router对同一个路径做不同的请求方法配置,如下,


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');
    });

其实这种方式在路由中间件中也是可以用的。针对特定的路由定制场景是特别适用的,可以更好的模块化、节省一些冗余的代码。

模板引擎

之前我们的示例代码中,都是简单是使用res.send给客户端发送简单的文本。如果我们想要客户端渲染一个自定义的页面,那该怎么做呢?

这就需要用到模板了。严格来说,express使用的模板属于服务端模板,因为它是在服务端解析完毕,发送给客户端进行渲染的。express支持很多模板引擎,基本上将市面上常见模板引擎一网打尽了。express默认的模板引擎是jade,一款非常优雅的模板引擎。后面博主会有文章介绍jade。

言归正传,我们在express中如何使用模板引擎来加载模板呢?其实很简单,三步走即可,

  1. 设置好模板的静态路径
  2. 设置好渲染的模板引擎
  3. 在需要的地方渲染你的模板

具体看下面的示例代码,


// 省略无关代码
// 设置模板文件夹的路径
app.set('views', path.join(__dirname, 'views'));
// 设置模板文件的后缀名
app.set('view engine', 'jade');
app.get('/', function (req, res){
    res.render('index');
});
app.get('/about', function(req, res) {
    res.render('about');
});
app.get('/article', function(req, res) {
    res.render('article');
});
// 省略无关代码

我们在views目录下有3个模板,分别为views/index.jadeviews/about.jadeviews/article.jade,当请求不同的路径时,我们会发送不同的模板给前端渲染。

如果你使用的是别的模板引擎,请参阅模板引擎针对express的相关说明。

本文由于篇幅原因,不会关注express的方方面面,更多的内容请参考官网的API Reference

NodeJS模块全面指南
通读SuperAgent文档
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }