第四章:Express.js - 开启高效 API 构建之路

第四章:Express.js - 开启高效 API 构建之路

摘要: 在前几章中,我们掌握了 Node.js 的核心能力,并搭建了一个专业的项目结构。但我们仍然在使用底层的 http 模块来处理请求,这对于构建复杂的应用而言,显得冗长且易错。本章,我们将正式引入 Node.js 生态中不可动摇的王者——Express.js。我们将学习如何使用这个轻量而强大的框架来快速定义路由、解析请求、构造响应,并彻底告别手动处理 URL 和请求头的繁琐工作。本章的目标是让你能够熟练运用 Express 构建出符合 RESTful 风格的 API 端点,为后续的数据交互和业务实现铺平道路。


在本章中,我们将完成从“手工作坊”到“工业化生产”的转变:

  1. 首先,我们将理解 Express.js 的核心价值,明白它为什么是 Node.js 的事实标准。
  2. 接着,我们将动手 安装并启动第一个 Express 服务,感受其无与伦比的简洁性。
  3. 然后,我们将掌握其 核心路由机制,学习如何优雅地定义 API 的“名词”(资源)与“动词”(HTTP 方法)。
  4. 之后,我们将深入解剖 req (请求) 和 res (响应) 对象,学习如何轻松获取客户端数据并与之对话。
  5. 最后,我们将引入 nodemon 这个开发利器,实现代码热重载,极大地提升开发效率。

4.1. Express.js — Node.js 的“标准”Web 框架

痛点背景: 回顾一下我们在 2.5 节中用原生 http 模块创建的服务器。我们需要手动编写 if/else 逻辑来判断 req.url,手动设置 Content-Type 响应头,手动将 JSON 对象字符串化… 想象一下,如果应用有几十个 API 接口,这个文件将变得多么臃肿和混乱。

解决方案: Express.js 正是解决这一问题的完美答案。它是一个 极简且灵活的 Node.js Web 应用框架,提供了一套强大特性,帮助我们创建健壮的 API 和 Web 应用。它本身不提供繁杂的功能,而是通过其核心的 中间件架构,允许我们按需引入功能。

可以这样理解:Node.js 的 http 模块给了我们制造汽车零件(处理 TCP 连接、HTTP 报文)的能力,而 Express 则提供了一个坚固、可靠的“汽车底盘”(路由系统、中间件管道),让我们能专注于设计和制造车身、内饰和引擎(我们的业务逻辑)。


4.2. 安装与启动:三步点亮你的第一个 Express 服务

承上启下: 理论讲完,让我们立刻上手。我们将沿用第三章建立的项目结构,并在 src 目录下创建一个 app.js 作为 Express 应用的入口文件。

项目结构上下文:

1
2
3
4
5
6
my-awesome-api/
├── src/
│ └── app.js # <-- 我们将在这里编写代码
├── .env
├── .gitignore
└── package.json

第一步:安装 Express
在你的项目根目录下打开终端,运行以下命令:

1
npm install express

第二步:创建 Express 应用 (app.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// file: src/app.js
// 1. 导入 Express 模块
import express from 'express';

// 2. 创建 Express 应用实例
const app = express();

// 3. 定义服务器监听的端口
const PORT = 3000;

// 4. 定义一个最简单的根路由
// 当客户端以 GET 方法请求 '/' 路径时,执行回调函数
app.get('/', (req, res) => {
// 使用 res.send() 方法向客户端发送响应
res.send('<h1>你好,Express!</h1>');
});

// 5. 启动服务器,并监听指定端口
app.listen(PORT, () => {
console.log(`🚀 服务器已启动,正在监听 http://localhost:${PORT}`);
});

第三步:启动服务
在终端中运行:

1
node src/app.js

现在,打开浏览器访问 http://localhost:3000,你将看到 “你好,Express!”。就是这么简单!我们用比原生 http 模块少得多的代码,实现了一个更清晰、更具可读性的 Web 服务器。


4.3. 核心路由:定义 API 的“动词”与“名词”

痛点背景: 我们的 http 服务器使用 if/else 来区分 URL,这显然无法扩展。我们需要一种能清晰地将 HTTP 方法 (GET, POST, PUT, DELETE)URL 路径 (资源) 绑定到特定处理逻辑的方式。

解决方案: Express 的路由系统完美地体现了 RESTful API 的设计思想。它提供了一系列与 HTTP 方法同名的函数,让路由定义变得极其直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import express from 'express';
const app = express();
const PORT = 3000;

// 路由就像是应用的“路标”,告诉 Express 如何处理不同路径和方法的请求

// GET /messages -> 获取所有消息
app.get('/messages', (req, res) => {
res.json({ message: '获取所有消息' });
});

// POST /messages -> 创建一条新消息
app.post('/messages', (req, res) => {
res.status(201).json({ message: '创建一条新消息成功' });
});

// PUT /messages/:id -> 更新指定 ID 的消息
app.put('/messages/:id', (req, res) => {
res.json({ message: `更新 ID 为 ${req.params.id} 的消息` });
});

// DELETE /messages/:id -> 删除指定 ID 的消息
app.delete('/messages/:id', (req, res) => {
res.json({ message: `删除 ID 为 ${req.params.id} 的消息` });
});

app.listen(PORT, () => {
console.log(`🚀 服务器已启动,正在监听 http://localhost:${PORT}`);
});

注意 :id 这种语法。这是 Express 的 动态路由参数,它允许我们匹配像 /messages/123/messages/abc 这样的路径,并通过 req.params.id 来获取这部分动态的值。


4.4. 解剖请求 (req) 对象:获取客户端的所有信息

痛点背景: API 的核心是数据交互。客户端会通过多种方式向服务器传递数据:在 URL 路径中(如 /users/123)、作为查询参数(如 /search?q=nodejs),或者放在请求体中(如提交一个 JSON 表单)。我们需要一个统一、便捷的方式来获取这些数据。

解决方案: Express 极大地简化了数据提取工作,它将所有请求信息解析并挂载到了 req (request) 对象上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import express from 'express';
const app = express();

// !!! 关键一步:要解析 JSON 格式的请求体,必须使用这个中间件
app.use(express.json());

// 1. 获取动态路由参数 (Route Params)
// 例如:访问 GET http://localhost:3000/users/42
app.get('/users/:userId', (req, res) => {
const { userId } = req.params; // userId 将是 "42"
res.send(`你正在请求用户 ID 为: ${userId} 的信息`);
});

// 2. 获取查询字符串参数 (Query String)
// 例如:访问 GET http://localhost:3000/articles?page=2&limit=10
app.get('/articles', (req, res) => {
const { page, limit } = req.query; // page 是 "2", limit 是 "10"
res.send(`你正在请求第 ${page} 页的文章,每页 ${limit} 篇`);
});

// 3. 获取请求体 (Request Body)
// 例如:发送 POST http://localhost:3000/login 并附带 JSON Body
// { "username": "awakening", "password": "123" }
app.post('/login', (req, res) => {
const { username, password } = req.body;
res.json({
message: '登录成功',
user: username
});
});

app.listen(3000);

陷阱警告: 如果你发现 req.body 总是 undefined99% 的原因是你忘记了添加 app.use(express.json());。这是一个非常重要的中间件,我们将在下一章深入探讨。


4.5. 构造响应 (res) 对象:与客户端的优雅对话

痛点背景: 我们需要向客户端返回不同类型的内容(HTML, JSON),并设置不同的 HTTP 状态码来表示操作结果(如 200 成功, 201 创建成功, 404 未找到, 500 服务器错误)。

解决方案: Express 的 res (response) 对象提供了一系列链式调用的方法,让响应的构建过程既简单又富有表现力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import express from 'express';
const app = express();
app.use(express.json());

const users = [{ id: 1, name: 'Alice' }];

app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));

if (user) {
// 1. res.json(): 发送 JSON 响应,自动设置 Content-Type
res.json(user);
} else {
// 2. res.status(): 设置 HTTP 状态码,可以链式调用
res.status(404).json({ error: '用户未找到' });
}
});

app.post('/users', (req, res) => {
if (!req.body.name) {
return res.status(400).json({ error: '用户名是必需的' });
}
const newUser = {
id: users.length + 1,
name: req.body.name,
};
users.push(newUser);
// 3. 链式调用:设置 201 (Created) 状态码并返回新创建的用户
res.status(201).json(newUser);
});

app.listen(3000);

4.6. 开发体验优化:nodemon 实现热重载

痛点背景: 在开发过程中,我们每修改一行代码,都必须手动停止服务器(Ctrl + C),然后重新启动 (node src/app.js) 才能看到效果。这个重复的动作极大地打断了我们的心流,降低了开发效率。

解决方案: nodemon 是一个专门为此而生的工具。它会监视项目中的文件变化,一旦检测到文件被保存,它就会自动重启 Node.js 应用。

第一步:安装 nodemon
它是一个开发依赖,所以我们使用 --save-dev (或 -D) 标志。

1
npm install nodemon --save-dev

第二步:配置 package.json
scripts 部分添加一个 "dev" 命令。

1
2
3
4
5
6
7
8
9
// package.json
{
// ...
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js"
},
// ...
}

第三步:启动开发服务器
现在,我们不再使用 node 命令,而是运行我们新定义的脚本:

1
npm run dev

现在,每当你修改并保存 src/ 目录下的任何文件,nodemon 都会在终端里自动重启服务器。你的开发体验将得到质的飞跃!


4.7. 本章核心速查总结

分类关键项核心描述
核心实例express()调用 express 函数,创建一个新的 Express 应用实例。
服务器app.listen(port, callback)启动服务器,绑定并监听指定端口上的连接。
路由app.METHOD(path, handler)定义路由。METHOD 是小写的 HTTP 方法,如 app.get, app.post
请求 (req)req.params包含映射到命名路由“参数”的对象,如 /:id
req.query包含 URL 查询字符串中的参数的对象,如 ?key=value
req.body包含在请求体中提交的数据。需要 express.json() 中间件
响应 (res)res.send([body])发送 HTTP 响应,内容可以是多种类型。
res.json([body])(推荐) 发送 JSON 响应,并自动设置正确的 Content-Type 头。
res.status(code)设置 HTTP 响应的状态码,支持链式调用。
开发工具nodemon监视文件变化并自动重启应用的开发工具。

4.8. 高频面试题与陷阱

面试官深度追问
2025-09-14

很多初学者会混淆 Node.js 和 Express.js。你能用你的理解,清晰地解释一下它们之间的关系吗?

当然。Node.js 是一个 JavaScript 运行时环境。它就像是一个“地基”,提供了让 JavaScript 在服务器端运行起来所需要的一切底层能力,比如 V8 引擎、事件循环、以及与操作系统交互的 API(如 fshttp 模块)。但 Node.js 本身并不关心你如何组织 Web 应用的路由或业务逻辑。

嗯,那 Express 在这个地基上扮演了什么角色?

Express.js 是一个构建在 Node.js 的 http 模块之上的 Web 应用框架。它就像是在地基之上建造的“房屋骨架”。它没有重新发明轮子,而是利用 Node.js 提供的底层能力,封装出了一套更高级、更易于使用的 API,专门用来处理 Web 开发中的常见任务,比如路由管理、请求解析、中间件处理等等。

很好的比喻。所以,可以说“没有 Node.js 就没有 Express”,但“只有 Node.js 而没有 Express”依然可以开发 Web 应用,只是会非常繁琐,是这样吗?

完全正确。我们可以只用 Node.js 的 http 模块来构建一个 Web 服务器,但需要手动处理所有事情。Express 极大地简化了这个过程,让我们能更专注于业务逻辑本身,而不是底层的 HTTP 协议细节。所以,它们是“运行时”和“框架”的关系,而不是替代关系。