六:Express 框架——让后端开发更简单

一、回顾与本篇目标

前面五篇,我们用 Node.js 原生的 http 模块写了一个能处理路由、解析查询参数、响应 GET 和 POST 请求的后端服务。功能是跑起来了,但你也一定感觉到了——很多地方写起来很啰嗦。

比如:判断请求路径要手动写 if...else,解析查询参数要手动调 url.parse(),读取 POST 请求体要手动监听 dataend 事件。这些是每个后端服务都需要的基本能力,但每次都要从头写一遍,既繁琐又容易出错。

这就是框架存在的意义:它把那些每个项目都要重复写的通用逻辑封装好,让你只需要关注业务本身——这个接口返回什么数据、那个接口做什么处理。

在 Node.js 生态中,Express 是最成熟、最流行、社区最庞大的后端框架。它的理念是轻量、简洁、灵活。很多其他框架(如 Next.js、Nuxt 的服务端部分)底层都受了 Express 的影响。

本篇的目标:

  1. 安装 Express 并启动第一个 Express 服务
  2. 用 Express 重写之前的路由——你会发现代码量大幅减少
  3. 理解 Express 最核心的概念——中间件
  4. 学会用 Express 处理静态文件、JSON 请求体、路由模块化

二、npm:安装第三方模块的包管理器

在正式使用 Express 之前,需要先了解一个工具——npm

npmNode Package Manager 的缩写。它是 Node.js 自带的包管理工具。之前我们用的 httpfspathurl 都是 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.urlrequest.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 请求、路径是 / 的时候,执行这个回调函数”。回调函数接收 requestresponse 两个参数,和原生 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.urlapp.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/123request.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 请求时,读取请求体是一个麻烦事——需要手动监听 dataend 事件,自己拼接数据块,然后自己解析。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.bodyundefined

在 Postman 中测试:选择 POST 方法,地址输入 http://localhost:3000/api/user,Body 选 rawJSON,输入 {"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');
});

运行并测试:

  1. 终端执行 node app.js
  2. 浏览器访问 http://localhost:3000 → 返回的是 public/index.html 的内容。
  3. 浏览器访问 http://localhost:3000/api/users → 返回用户列表的 JSON。
  4. 用 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 文件,实现增删改查。你会真正体会到后端最核心的工作:接收请求 → 操作数据 → 返回结果。

后端零基础入门,每周更新。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容