1. Express 入门

1.1 什么是 Express

Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供一系列强大的特性,帮助你快速创建各种 Web 和移动设备应用。 Express 中的路由中间件为我们的开发带来极大便利。

简单来说, Express 就是一个第三方模块,专门用来创建 HTTP服务。

1.2 相关网站

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

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 平台

results matching ""

    No results matching ""