Express框架

发布时间 2023-07-08 20:31:49作者: 富贵er

用内置 http 模块创建服务器

创建 server.js 文件,代码如下:

const http = require('http');

const hostname = 'localhost';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/html');
  res.end('Hello World\n');
});

server.listen(port, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

可以发现,直接用内置的 http 模块去开发服务器有以下明显的弊端:

  • 需要写很多底层代码——例如手动指定 HTTP 状态码和头部字段,最终返回内容。如果我们需要开发更复杂的功能,涉及到多种状态码和头部信息(例如用户鉴权),这样的手动管理模式非常不方便
  • 没有专门的路由机制——路由是服务器最重要的功能之一,通过路由才能根据客户端的不同请求 URL 及 HTTP 方法来返回相应内容。但是上面这段代码只能在 http.createServer 的回调函数中通过判断请求 req 的内容才能实现路由功能,搭建大型应用时力不从心

由此就引出了 Express 对内置 http 的两大封装和改进:

  • 更强大的请求(Request)和响应(Response)对象,添加了很多实用方法
  • 灵活方便的路由的定义与解析,能够很方便地进行代码拆分

用 Express 搭建服务器

在开始用 Express 改写上面的服务器之前,我们先介绍一下上面提到的两大封装与改进。

更强大的 Request 和 Response 对象

首先是 Request 请求对象,通常我们习惯用 req 变量来表示。下面列举一些 req 上比较重要的成员(如果不知道是什么也没关系哦):

  • req.body:客户端请求体的数据,可能是表单或 JSON 数据
  • req.params:请求 URI 中的路径参数
  • req.query:请求 URI 中的查询参数
  • req.cookies:客户端的 cookies

然后是 Response 响应对象,通常用 res 变量来表示,可以执行一系列响应操作,例如:

// 发送一串 HTML 代码
res.send('HTML String');

// 发送一个文件
res.sendFile('file.zip');

// 渲染一个模板引擎并发送
res.render('index');

路由机制

客户端(包括 Web 前端、移动端等等)向服务器发起请求时包括两个元素:路径(URI)以及 HTTP 请求方法(包括 GET、POST 等等)。路径和请求方法合起来一般被称为 API 端点(Endpoint)。而服务器根据客户端访问的端点选择相应处理逻辑的机制就叫做路由。

在 Express 中,定义路由只需按下面这样的形式:

app.METHOD(PATH, HANDLER)

其中:

  • app 就是一个 express 服务器对象
  • METHOD 可以是任何小写的 HTTP 请求方法,包括 get、post、put、delete 等等
  • PATH 是客户端访问的 URI,例如 / 或 /about
  • HANDLER 是路由被触发时的回调函数,在函数中可以执行相应的业务逻辑

正式实现

到了动手的时候了,我们用 Express 改写上面的服务器,代码如下:

const express = require('express');

const hostname = 'localhost';
const port = 3000;

const app = express();
app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(port, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

在上面的代码中,我们首先用 express() 函数创建一个 Express 服务器对象,然后用上面提到的路由定义方法 app.get 定义了主页 / 的路由,最后同样调用 listen 方法开启服务器。

中间件

理解中间件

Express 的简化版中间件流程如下图所示:

首先客户端向服务器发起请求,然后服务器依次执行每个中间件,最后到达路由,选择相应的逻辑来执行。

提示
这个是一个简化版的流程描述,目的是便于你对中间件有个初步的认识,在后面的章节中我们将进一步完善这一流程。

有两点需要特别注意:

  • 中间件是按顺序执行的,因此在配置中间件时顺序非常重要,不能弄错
  • 中间件在执行内部逻辑的时候可以选择将请求传递给下一个中间件,也可以直接返回用户响应

在 Express 中,中间件就是一个函数:

function someMiddleware(req, res, next) {
  // 自定义逻辑
  next();
}

三个参数中,req 和 res 就是前面提到的 Request 请求对象和 Response 响应对象;而 next 函数则用来触发下一个中间件的执行。

注意
如果忘记在中间件中调用 next 函数,并且又不直接返回响应时,服务器会直接卡在这个中间件不会继续执行下去哦!

在 Express 使用中间件有两种方式:全局中间件和路由中间件。

全局中间件

通过 app.use 函数就可以注册中间件,并且此中间件会在用户发起任何请求都可能会执行,例如:

app.use(someMiddleware);

路由中间件

通过在路由定义时注册中间件,此中间件只会在用户访问该路由对应的 URI 时执行,例如:

app.get('/middleware', someMiddleware, (req, res) => {
  res.send('Hello World');
});

那么用户只有在访问 /middleware 时,定义的 someMiddleware 中间件才会被触发,访问其他路径时不会触发。

使用子路由拆分逻辑

当我们的网站规模越来越大时,把所有代码都放在 server.js 中可不是一个好主意。“拆分逻辑”(或者说“模块化”)是最常见的做法,而在 Express 中,我们可以通过子路由 Router 来实现。

const express = require('express');
const router = express.Router();

express.Router 可以理解为一个迷你版的 app 对象,但是它功能完备,同样支持注册中间件和路由:

// 注册一个中间件
router.use(someMiddleware);

// 添加路由
router.get('/hello', helloHandler);
router.post('/world', worldHandler);

最后,由于 Express 中“万物皆中间件”的思想,一个 Router 也作为中间件加入到 app 中:

app.use('/say', router);

这样 router 下的全部路由都会加到 /say 之下,即相当于:

app.get('/say/hello', helloHandler);
app.post('/say/world', worldHandler);

正式实现

到了动手环节,首先创建 routes 目录,用于存放所有的子路由。
创建 routes/api.js,代码如下:

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.json({ name: '图雀社区', website: 'https://tuture.co' });
});

router.post('/new', (req, res) => {
  res.status(201).json({ msg: '新的篇章,即将开始' });
});

module.exports = router;

最后我们把 server.js 中老的路由定义全部删掉,替换成刚刚实现的两个 Router,代码如下:

const express = require('express');
const apiRouter = require('./routes/api');

const hostname = 'localhost';
const port = 3000;

const app = express();

app.use('/', indexRouter);
app.use('/api', apiRouter);

app.use('*', (req, res) => {
  res.status(404).render('404', { url: req.originalUrl });

});