一、回顾与本篇目标
前面五篇,我们用 Node.js 原生的 http 模块写了一个能处理路由、解析查询参数、响应 GET 和 POST 请求的后端服务。功能是跑起来了,但你也一定感觉到了——很多地方写起来很啰嗦。
比如:判断请求路径要手动写 if...else,解析查询参数要手动调 url.parse(),读取 POST 请求体要手动监听 data 和 end 事件。这些是每个后端服务都需要的基本能力,但每次都要从头写一遍,既繁琐又容易出错。
这就是框架存在的意义:它把那些每个项目都要重复写的通用逻辑封装好,让你只需要关注业务本身——这个接口返回什么数据、那个接口做什么处理。
在 Node.js 生态中,Express 是最成熟、最流行、社区最庞大的后端框架。它的理念是轻量、简洁、灵活。很多其他框架(如 Next.js、Nuxt 的服务端部分)底层都受了 Express 的影响。
本篇的目标:
- 安装 Express 并启动第一个 Express 服务
- 用 Express 重写之前的路由——你会发现代码量大幅减少
- 理解 Express 最核心的概念——中间件
- 学会用 Express 处理静态文件、JSON 请求体、路由模块化
二、npm:安装第三方模块的包管理器
在正式使用 Express 之前,需要先了解一个工具——npm。
npm 是 Node Package Manager 的缩写。它是 Node.js 自带的包管理工具。之前我们用的 http、fs、path、url 都是 Node.js 内置的模块,不需要安装。但 Express 是第三方模块,需要用 npm 来下载和安装。
可以这样理解:
- 内置模块:手机自带的计算器、日历、时钟。买到手机就能用。
- 第三方模块:从应用商店下载的微信、淘宝、地图。需要主动下载安装。
- npm:就是这个应用商店。你告诉它你要装什么,它帮你下载并管理好。
初始化项目
在使用 npm 安装任何第三方模块之前,需要先把当前文件夹初始化为一个 npm 项目。这个操作只需要做一次。
打开终端,进入你的项目文件夹,执行:
npm init -y
解释:
npm init:初始化命令,会问一系列问题(项目名、版本、描述等)。-y:对所有问题都回答“是”,使用默认值。省得一个个敲回车。
执行完后,文件夹里会多出一个 package.json 文件。这个文件记录了你的项目信息和安装了哪些第三方模块。现在先不用深究它的内容,只需要知道它的存在。
安装 Express
在终端里执行:
npm install express
等待几秒钟,安装完成后你会发现:
- 文件夹里多了一个
node_modules文件夹。这里面存放了 Express 以及它依赖的所有代码。这个文件夹通常很大,不要手动修改它。 package.json里多了一行"dependencies": { "express": "^4.x.x" },记录了“这个项目依赖 Express”。
以后如果把代码分享给别人,不需要把 node_modules 发过去。对方拿到代码后,在项目文件夹里执行 npm install,npm 就会根据 package.json 的记录自动把所有依赖下载好。
三、第一个 Express 服务
安装好 Express 之后,用 Express 重写我们最熟悉的“启动一个 HTTP 服务并返回一段文字”。
新建 express-demo.js:
// express-demo.js —— 第一个 Express 服务
// 1. 加载 express 模块
const express = require('express');
// 2. 创建一个 Express 应用
const app = express();
// 3. 定义一个路由:当浏览器以 GET 方式访问 / 时,执行回调函数
app.get('/', function (request, response) {
response.send('你好,这是 Express 返回的内容!');
});
// 4. 启动服务,监听 3000 端口
app.listen(3000, function () {
console.log('Express 服务已启动,访问 http://localhost:3000');
});
在终端执行 node express-demo.js,浏览器访问 http://localhost:3000,效果和之前一样。
和原生 HTTP 模块的对比:
| 原生 http 模块 | Express |
|---|---|
http.createServer(callback) |
express() 创建应用 |
手动判断 request.url 和 request.method |
app.get(path, callback) 直接指定方法和路径 |
response.writeHead() + response.end() |
response.send() 一行搞定 |
逐行解释:
const express = require('express'):加载 express 模块。注意这次没有写'./'前缀,因为 Express 是第三方模块,npm 把它放在了node_modules里,Node.js 会自动去那里找。const app = express():创建一个 Express 应用对象。app是你整个后端的核心,所有路由、中间件、配置都挂在这个对象上。app.get('/', callback):定义一个路由。意思是“当收到 GET 请求、路径是/的时候,执行这个回调函数”。回调函数接收request和response两个参数,和原生http模块类似,但 Express 在它们上面加了更多方便的方法。response.send():发送响应。和原生的response.end()相比,send()更智能:你传字符串,它会自动设置Content-Type: text/html并加上正确的字符编码;你传对象或数组,它会自动转成 JSON 并设置Content-Type: application/json。app.listen(3000, callback):启动服务,监听 3000 端口。和http模块的server.listen完全一样。
四、用 Express 定义多种路由
上一篇我们用 if...else 和路由表来处理不同路径。在 Express 中,这项工作变得极其简单——每个路由一行代码:
// express-routes.js —— Express 路由演示
const express = require('express');
const app = express();
// 首页
app.get('/', function (request, response) {
response.send('<h1>首页</h1>');
});
// 关于页面
app.get('/about', function (request, response) {
response.send('<h1>关于我们</h1><p>这是一个演示项目。</p>');
});
// 返回 JSON 数据
app.get('/api/users', function (request, response) {
// response.send 会自动把对象转成 JSON
response.send([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
]);
});
// 处理 POST 请求
app.post('/register', function (request, response) {
response.send('注册接口收到 POST 请求。');
});
// 如果没有任何路由匹配,返回 404
// 这个要放在所有路由的后面
app.use(function (request, response) {
response.status(404).send('404 - 页面未找到');
});
app.listen(3000, function () {
console.log('服务已启动,访问 http://localhost:3000');
});
重点变化:
- 不需要手动判断
request.url:app.get('/about', ...)直接指定了路径,只有访问/about才会触发这个回调。 - 返回 JSON 极其简单:直接把数组传给
response.send(),Express 自动帮你转成 JSON 字符串并设置好 Content-Type。 - 定义 POST 路由同样简单:
app.post()和app.get()用法完全一样。 - 404 处理:
app.use()定义了一个“兜底”处理函数。Express 按代码顺序匹配路由,如果前面所有路由都没匹配上,最后就会执行这个兜底函数,返回 404。
五、路由参数:Express 帮你自动解析
上一篇我们费了不少功夫手动解析路径参数(如 /user/123 中的 123)。Express 提供了极其优雅的写法——用冒号前缀定义参数:
// express-params.js —— 路由参数演示
const express = require('express');
const app = express();
// :id 表示这个位置是一个动态参数,名字叫 id
app.get('/user/:id', function (request, response) {
// 通过 request.params 获取路径参数
let userId = request.params.id;
response.send('你请求的用户 ID 是:' + userId);
});
// 可以有多个参数
app.get('/article/:category/:articleId', function (request, response) {
let category = request.params.category;
let articleId = request.params.articleId;
response.send('分类:' + category + ',文章 ID:' + articleId);
});
app.listen(3000, function () {
console.log('测试以下地址:');
console.log(' http://localhost:3000/user/123');
console.log(' http://localhost:3000/user/456');
console.log(' http://localhost:3000/article/tech/789');
});
关键点:
'/user/:id':冒号后面的id是参数名。这个路由可以匹配/user/123、/user/abc、/user/任意字符串。request.params:Express 自动帮你把路径参数解析成了对象。访问/user/123,request.params就是{ id: '123' }。- 注意参数值永远是字符串,如果需要数字,用
Number()转换。
六、查询参数:Express 同样帮你解析好了
上一篇我们用了 url.parse() 来解析查询参数(?keyword=手机&page=2)。在 Express 中,查询参数直接挂在 request.query 上,不需要任何额外操作:
// express-query.js —— 查询参数演示
const express = require('express');
const app = express();
app.get('/search', function (request, response) {
let keyword = request.query.keyword || '未提供';
let page = request.query.page || 1;
response.send('搜索关键词:' + keyword + ',页码:' + page);
});
app.listen(3000, function () {
console.log('测试:http://localhost:3000/search?keyword=手机&page=3');
});
request.query 是一个对象,包含所有查询参数。它和 url.parse(url, true).query 的结果是一样的,但不需要你手动调用任何解析函数。
七、中间件:Express 最重要的概念
前面几篇在处理 POST 请求时,读取请求体是一个麻烦事——需要手动监听 data 和 end 事件,自己拼接数据块,然后自己解析。Express 用一个概念优雅地解决了这个问题:中间件。
什么是中间件
中间件本质上就是一个函数。它会在请求到达最终的路由处理函数之前,对请求和响应做一些预处理。
一个请求的处理流程可以想象成一条流水线:
请求进来
→ 中间件1(记录日志)
→ 中间件2(解析请求体)
→ 中间件3(检查登录状态)
→ 路由处理函数(返回数据)
每个中间件都可以选择:
- 把处理后的请求交给下一个中间件(调用
next())。 - 直接返回响应,终止后续处理(不调用
next())。
内置中间件:解析 JSON 请求体
Express 内置了一个非常重要的中间件——express.json()。它能把 POST 请求体中的 JSON 数据自动解析成 JavaScript 对象,挂在 request.body 上。
// express-json-body.js —— 解析 JSON 请求体
const express = require('express');
const app = express();
// 使用内置中间件:解析 JSON 格式的请求体
app.use(express.json());
// POST 路由:接收 JSON 数据
app.post('/api/user', function (request, response) {
// request.body 就是前端发来的 JSON 数据
// 前提是前端设置了 Content-Type: application/json
let newUser = request.body;
console.log('收到的新用户数据:', newUser);
response.send({
message: '用户创建成功',
user: newUser
});
});
app.listen(3000, function () {
console.log('用 Postman 发一个 POST 请求到 http://localhost:3000/api/user');
console.log('Body 选择 raw → JSON,输入 {"name":"张三","age":28}');
});
逐行解释:
app.use(express.json()):告诉 Express“在所有的路由处理之前,先用这个中间件解析请求体中的 JSON 数据”。app.use()是注册中间件的方法。这个中间件会检查请求的Content-Type,如果是application/json,就把请求体解析成对象挂到request.body上;如果不是,就跳过。request.body:前端发来的 JSON 数据被解析后的对象。如果没有express.json()中间件,request.body是undefined。
在 Postman 中测试:选择 POST 方法,地址输入 http://localhost:3000/api/user,Body 选 raw → JSON,输入 {"name":"张三","age":28},点击 Send。服务器会返回创建成功的消息,并把你发过去的数据原样返回。
自定义中间件:记录请求日志
除了使用 Express 内置的中间件,你也可以自己写中间件。下面写一个简单的日志中间件——每次有请求进来,在终端打印请求方法和路径:
// express-middleware-logger.js —— 自定义日志中间件
const express = require('express');
const app = express();
// 自定义中间件:记录每个请求的方法和路径
app.use(function (request, response, next) {
let now = new Date().toLocaleTimeString();
console.log('[' + now + '] ' + request.method + ' ' + request.url);
next(); // 重要!调用 next() 才会继续往下处理
});
app.get('/', function (request, response) {
response.send('首页');
});
app.get('/about', function (request, response) {
response.send('关于');
});
app.listen(3000, function () {
console.log('服务已启动,访问任意路径观察终端日志');
});
中间件函数的三个参数:
request:请求对象,和路由处理函数中的一样。response:响应对象,和路由处理函数中的一样。next:一个函数。调用next()表示“我处理完了,把请求交给下一个中间件或路由”。如果不调用next(),请求就会被挂起,浏览器一直转圈等不到响应。
每次访问任何路由,终端都会打印一行日志。这个中间件对所有路径都生效,因为它是通过 app.use() 注册的,没有指定路径。
八、静态文件:直接返回图片、CSS、JS
后端除了返回动态数据,也经常需要返回静态文件——图片、CSS 文件、前端 JS 文件。Express 提供了 express.static() 中间件来处理这个需求。
// express-static.js —— 静态文件服务
const express = require('express');
const app = express();
// 把 public 文件夹设置为静态文件目录
app.use(express.static('public'));
// 没有 app.get 也能访问 public 里的文件
app.listen(3000, function () {
console.log('服务已启动。');
console.log('把图片、CSS 文件放到 public 文件夹里,就可以通过浏览器直接访问');
});
假设你的项目结构是:
项目文件夹/
├── app.js
└── public/
├── style.css
├── logo.png
└── index.html
启动服务后,浏览器可以直接访问:
http://localhost:3000/style.css→ 返回style.css的内容http://localhost:3000/logo.png→ 返回图片http://localhost:3000/index.html→ 返回 HTML 页面
不需要为每个文件单独写路由,Express 自动处理了。这个功能在前后端分离的项目中非常常用——后端提供 API,前端打包后的文件放在静态目录里。
九、路由模块化:把路由拆分到独立文件
如果所有的路由都写在 app.js 里,项目大了之后这个文件会变得非常长。Express 提供了 express.Router() 来把路由拆分到多个文件。
第 1 步:创建路由文件 routes/users.js
// routes/users.js —— 用户相关的路由
const express = require('express');
const router = express.Router(); // 创建一个路由实例
// 定义用户相关的路由
router.get('/', function (request, response) {
response.send([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]);
});
router.get('/:id', function (request, response) {
response.send('用户详情,ID:' + request.params.id);
});
router.post('/', function (request, response) {
response.send('用户创建成功,数据:' + JSON.stringify(request.body));
});
module.exports = router; // 导出路由实例
第 2 步:在主文件 app.js 中挂载路由
// app.js —— 主入口
const express = require('express');
const app = express();
app.use(express.json());
// 加载用户路由模块,并挂载到 /api/users 路径
const usersRouter = require('./routes/users.js');
app.use('/api/users', usersRouter);
// 首页
app.get('/', function (request, response) {
response.send('首页');
});
app.listen(3000, function () {
console.log('服务已启动');
console.log(' GET /api/users → 用户列表');
console.log(' GET /api/users/123 → 用户详情');
console.log(' POST /api/users → 创建用户');
});
关键点:
express.Router()创建一个“迷你应用”,可以在上面定义路由。app.use('/api/users', usersRouter)把路由模块挂载到/api/users路径下。模块里定义的/会变成/api/users,模块里定义的/:id会变成/api/users/:id。- 这样
app.js只需要一行app.use就引入了整个用户模块,代码保持简洁。
十、本篇综合演示:用 Express 重写完整服务
下面是一个完整的 Express 项目,整合了本篇学到的所有内容:路由、路由参数、查询参数、JSON 解析、中间件、静态文件、路由模块化。
项目结构:
项目文件夹/
├── app.js ← 主入口
├── routes/
│ └── users.js ← 用户路由模块
└── public/
└── index.html ← 静态首页
public/index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Express 演示</title>
</head>
<body>
<h1>Express 后端服务已启动</h1>
<p>这个页面是从 public 文件夹返回的静态文件。</p>
</body>
</html>
routes/users.js(同上节,略)
app.js:
// app.js —— 完整的 Express 项目
const express = require('express');
const app = express();
// 1. 中间件:解析 JSON 请求体
app.use(express.json());
// 2. 中间件:记录请求日志
app.use(function (request, response, next) {
console.log('[' + new Date().toLocaleTimeString() + '] ' + request.method + ' ' + request.url);
next();
});
// 3. 静态文件
app.use(express.static('public'));
// 4. 挂载用户路由
const usersRouter = require('./routes/users.js');
app.use('/api/users', usersRouter);
// 5. 首页路由
app.get('/', function (request, response) {
response.send('<h1>Express 服务</h1><p>请访问 /api/users 查看用户接口。</p>');
});
// 6. 404 兜底
app.use(function (request, response) {
response.status(404).send('404 - 页面未找到');
});
// 7. 启动服务
app.listen(3000, function () {
console.log('服务已启动,访问 http://localhost:3000');
console.log('API 接口:');
console.log(' GET /api/users');
console.log(' GET /api/users/:id');
console.log(' POST /api/users');
});
运行并测试:
- 终端执行
node app.js。 - 浏览器访问
http://localhost:3000→ 返回的是public/index.html的内容。 - 浏览器访问
http://localhost:3000/api/users→ 返回用户列表的 JSON。 - 用 Postman 发送 POST 请求到
http://localhost:3000/api/users,Body 选 raw → JSON,输入任意用户数据,观察返回。
十一、本篇动手练习
练习 1:增加文章路由模块
仿照 routes/users.js,新建 routes/articles.js,实现以下接口:
GET /api/articles→ 返回文章列表(数组)GET /api/articles/:id→ 返回单篇文章详情POST /api/articles→ 创建新文章
在 app.js 中用 app.use('/api/articles', articlesRouter) 挂载。
练习 2:写一个简单的验证中间件
写一个中间件,检查请求是否带了一个叫 token 的查询参数。如果没带,直接返回 401 和“未授权”提示。把这个中间件应用到 /api/users 路由上(提示:app.use('/api/users', authMiddleware, usersRouter))。
练习 3:用 Postman 全面测试
确保综合演示的完整项目在运行。用 Postman 测试:
- GET 请求获取用户列表,观察状态码和响应格式。
- POST 请求创建用户,设置 Content-Type 为 JSON,发送数据,观察返回。
- 故意访问一个不存在的路径(如
/abc),观察 404 响应。
十二、本篇小结
这一篇我们正式引入了 Express 框架:
- npm:Node.js 的包管理器。
npm init -y初始化项目,npm install express安装第三方模块。package.json记录依赖。 - Express 基础:
express()创建应用,app.get()和app.post()定义路由,response.send()发送响应(自动处理字符串、HTML、JSON)。 - 路由参数:
/user/:id定义动态参数,通过request.params获取。 - 查询参数:
request.query直接获取,不需要手动解析。 - 中间件:Express 最核心的概念。内置中间件
express.json()解析 JSON 请求体;自定义中间件处理日志、验证等逻辑;express.static()提供静态文件服务。 - 路由模块化:
express.Router()创建独立路由模块,app.use()挂载到指定路径。 - 请求流程:请求 → 中间件链(按注册顺序) → 匹配路由 → 返回响应。
Express 大幅简化了 Node.js 后端开发。原来需要几十行代码才能做到的事,现在几行就能完成。接下来,我们要解决一个更核心的问题:数据到底存在哪里。下一篇,我们从最基础的文件读写开始,逐步引入数据库。
下一篇预告
下一篇——《数据存在哪里——文件读写》:在引入 MySQL 数据库之前,先用最简单的方式理解“持久化存储”。用文件模拟一个数据库——把数据存成 JSON 文件,实现增删改查。你会真正体会到后端最核心的工作:接收请求 → 操作数据 → 返回结果。
后端零基础入门,每周更新。













暂无评论内容