Express基础

基于Node.js平台,快速、开放、极简的web开发框架。

$ npm install --save express

快速入门

安装

# 创建目录并进入此目录将其作为当前工作目录
$ mdkri app && cd app

# 为应用创建package.json文件,默认应用入口点文件(entry point)为index.js,可修改为app.js。
$ npm init

$ cat package.json
{
  "name": "demo",
  "version": "1.0.0",
  "description": "express demo",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "junchow",
  "license": "MIT"
}


# 安装Express并保存到依赖列表中,即将模块添加到package.json中dependencies依赖列表中。
$ npm install express --save

# 临时安装Express不添加到依赖列表中
$ npm install express

案例

# 创建入口点(entity point)文件
$ vim app.js

var express = require('express');
var app = express();

app.get('/', function(req,res){
    res.send('hello');
});

var server = app.listen(3001, function(){
    var host = server.address().address;
    var port = server.address().port;

    console.log("app listening at http://%s:%s", host, port);
});

# 启动应用
$ node app.js

# 浏览器访问
http://127.0.0.1:3001

应用生成器

使用Express应用生成器(脚手架)可快速构建应用的骨架

# 全局安装Express应用生成器
$ npm install express-generator -g

# 查看Express应用生成器所支持的命名行选项
$ express -h

# 查看Express版本
$ express --version
4.15.5

# 当前工作目录下创建名为app的应用
$ express app

# 进入app应用并安装依赖
$ cd app && npm install

# 设置debug模式启动app应用,默认服务监听端口为3000.
# Linux
> set DEBUG=app && npm start
# Windows
$ DEBUG=app npm start

# 浏览器访问
http://127.0.0.1:3000

基础路由

路由(Routing)是由一个URI(路径)和特定HTTP方法(GET/POST/...)组成,涉及到应用如何响应客户端对某网络节点的访问。

每个路由可有一个或多个处理器函数,当路由匹配时函数将被触发执行。

路由的定义结构

app.METHOD(PATH, HANDLER)

- app Express实例
- METHOD HTTP请求方式
- PATH 服务端路径
- HANDLER 当路由匹配时需执行的处理函数

实例

app.get('/', function(req, res){
    res.send('Got a GET request');
});
app.post('/', function(req, res){
    res.send('Got a POST request');
});
app.put('/', function(req, res){
    res.send('Got a PUT request');
});
app.delete('/user', function(req, res){
    res.send('Got a DELETE request at /user');
});

静态资源

Express内置的express.static中间件可托管静态文件,例如CSS、JS、图片等文件。

将静态资源文件所在目录作为参数传递给express.static中间件,即可提供静态资源文件的访问。

# 设置public目录作为静态资源存放目录
app.use(express.static('public'));

设置完成后即可直接通过URL访问静态资源文件

http://127.0.0.1:3000/css/bootstrap.css
http://127.0.0.1:3000/js/jquery.js
...

若静态资源存放在多个目录下面,则可多次调用express.static中间件。

app.use(express.static('public'));
app.use(express.static('static'));

访问静态资源文件时,express.static中间件会根据目录添加的顺序查找所需文件。

若希望通过express.static中间件访问的文件都存在一个"虚拟(virtual)"目录(即目录根本不存在)下面,可通过为静态资源目录指定一个挂载路径的方式来实现。

app.use('/static', express.static('public'));

访问方式

http://127.0.0.1:3000/static/js/jquery.js
http://127.0.0.1:3000/static/css/bootstrap.css

使用指南

路由

路由指如何定义应用的端点(URIs)以及如何响应客户端请求。

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

路由方法

路由方法源于HTTP请求方法,和Express实例相关联。

app.get('/', function(req, res){
    res.send('Got a GET request to the homepage');
});

app.post('/', function(req, res){
    res.send('Got a POST request to the homepage');
});

app.all() 是一个特殊的路由方法,没有任何HTTP方法与之对应,其作用是相对于下一个路径上的所有请求加载中间件。

# 来自“/secret”的请求,任何HTTP请求,句柄都将执行。
app.all('/secret', function(req, res, next){
    console.log('Accessing the secret section...');
    next();//pass control to the next handler
});

请求路径

路由路径和请求一起定义定义了请求的端点,它可以是字符串、字符串模式、正则表达式。但是,查询字符串不是路由的一部分。

字符串模式路由路径

//匹配 /acd 和 /bcd
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、/ab1cd、/ab1212121dafacd...
app.get('/ab*cd', function(req, res){
    res.send('ab*cd');
});

//匹配/abe 和 /abcde
app.get('/ab(cd)?e', function(req, res){
    console.log('ab(cd)?e');
});

正则表达式路由路径

// 匹配任何路径中含有a的路径
app.get(/a/, function(req, res){
    console.log('/a/');
});

// 匹配/butterfly、/dragonfly,不匹配/buterflyman、/dragonfly man...
app.get(/.*fly$/, function(req, res){
    console.log('/.*fly$/');
});

路由句柄

路由句柄为请求处理提供多个回调函数,其行为类似中间件。

路由句柄与中间件的唯一区别在于回调函数有可能调用next('route')方法而略过其他路由回调函数。

可利用此机制为路由定义前提条件,若现有路径上继续执行无意义,则可将控制权交给剩下的路径。

路由句柄形式包括一个函数、一个函数数组、两者混用。


# 使用一个回调函数处理路由
app.get('/user/create', function(req, res){
    res.send('hello from user create');
});

# 使用多个回调函数处理路由,需指定next对象。
app.get('/user/create', function(req, res, next){
    console.log('response will be send by the next function...');
    next();
}, function(req, res){
    res.send('hello from user create');
});

# 使用回调函数数组处理路由
app.get('/user/create', [callback1, callback2, callback3]);
var callback1 = function(req, res, next){
    console.log('callback1');
    next();
};
var callback2 = function(req, res, next){
    console.log('callback2');
    next();
};
var callback3 = function(req, res){
    res.send('hello from user create');
};

# 混合使用函数和函数数组方式处理路由
app.get('/user/create', [callback1, callback2], function(req, res, next){
    console.log('response will send by the next function...');
    next();
}, function(req, res){
    res.send('hello from user create');
})

响应方法

响应对象(res)的方法是向客户端返回响应,终结请求响应的其你去。若路由句柄中一个方法也不调用,癞子客户端的请求会一直挂起。

res.download() 提示下载文件
res.end() 终结响应处理流程
res.json() 发送一个JSON格式的响应
res.jsonp() 发送一个支持JSONP的JSON格式的响应
res.redirect() 重定向请求
res.render() 渲染视图模板
res.send() 发送各种类型的响应
res.sendFile() 以八位字节流形式发送文件
res.sendStatus() 设置响应状态码,并将其以字符串形式作为响应的一部分发送。

app.router()

可使用app.router()创建路由路径的链式路由句柄,由于路径在一个地方指定,有助于创建模块化的路由,减少代码冗余和拼写错误。

# 定义链式路由句柄
app.route('/user')
.get(function(req, res){
    res.send('Got a GET request from user');
})
.post(function(req, res)){
    res.send('Got a POST request from user create');
}
.put(function(req, res){
    res.send('Got a PUT request from user update');
});

express.Router

使用express.Router类创建模块化、可挂载的路由句柄。

Router实例是一个完整的中间件和路由系统,因此又称之为"mini-app"。

$ vim tmp.js

var express = require('express');
var app = express();

//路由使用timelog中间件
router.use(function timelog(req, res, next){
    console.log("time: ", Date.now());
});

// 定义路由
router.get('/', function(req, res){
    res.send('homepage');
});
router.get('/user', function(req, res){
    res.send('user');
});

module.exports = router;

# 应用中加载路由模块
var tmp = require('./tmp');
app.use('/tmp', tmp);

中间件

中间件(middleware)是一个函数,可访问请求对象(request object)、响应对象(response object),和web应用中处于请求-响应循环流程中的中间件,一般被命名为next的变量。

Express是一个自身功能极简,完全是由路由和中间件构成的一个web开发框架。

从本质上来说,一个Express应用就是在调用各种中间件。

中间件的功能包括

  • 执行任何代码
  • 修改请求和响应对象
  • 终结请求-响应循环
  • 调用堆栈中的下一个中间件

若当前中间件没有终结请求-响应循环,则必须调用next()方法将控制权交给下一个中间件,否则请求就会挂起。

中间件可分为

  • 应用级中间件
  • 路由级中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

应用级中间件

应用级中间件可绑定到app对象,使用app.use()app.METHOD(),其中METHOD是需要处理的HTTP请求的方法,例如GET、POST、PUT等。

var express = require('express');
var app = express();

// 没有挂载路由的中间件,应用的每个请求都会执行该中间件。
app.use(function(req, res, next){
    console.log("time: ", Date.now());
    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("reqeuest 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){
    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('router')方法将控制权交给下一个路由。

next('router')只对使用app.VERB()router.VERB()加载的中间件有效。

//一个中间件栈,处理指向/user/:id的GET请求
app.get('/user/:id', function(req, res, next){
    //若user_id为0则跳到下一个路由,否认将控制权交给栈中下一个中间件。
    if(req.params.id==0){
        next('router');//在中间件栈中跳过剩余中间件,调用next('router')方法将控制权交给下一个路由。
    }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 express = require('express');
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("requst type: ", req.method);
    next();
});

//将路由挂载到应用
app.use('/', router);

错误处理中间件

# 错误处理中间件和其他中间件定义类似,仅需使用4个参数,其签名是(err,req,res,next)
app.use(function(err, req, res, next){
    console.error(err.stack);
    res.status(500).send('something broken');
});

错误处理中间件必须要有4个参数,定义错误处理中间件时必须使用这4个参数。即时无需next对象也必须在签名中声明它,否则中间件会被识别为一个常规中间件,无法处理错误。

系统内置中间件

从Express4.x开始,已不在依赖Connect。除了express.static外,Express以前内置中间件现已全部单独作为模块安装使用。

express.static(root, [options])

express.static是Express唯一内置的中间件,它基于serve-static负责在Express应用中提供托管静态资源。

第三方中间件

$ npm install cookie-parse

$ vim app.js
var express = require('express');
var app = express();

// 加载第三方中间件cookie-parser
var cookieParser = require('cookie-parser');
// 记载用于解析cookie的中间件
app.use(cookieParser());
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335