06
Express:最流行的 Web 框架
Express 4.x / 5.x
上一章那个原生 http 服务器,自己判断 if(req.url === '/') 写不了几行就乱套。Express 就是把这些脏活打包好的框架——你只要说"GET 这个路径时干嘛、POST 那个路径时干嘛",剩下的路由匹配、解析请求体,它全包了。它极简、灵活,是 Node 后端的入门标配(4.x 最稳,5.x 已正式发布)。
十分钟跑起来一个 Express 应用
第一步:初始化并安装
mkdir my-api && cd my-api
npm init -y
npm i express
第二步:app.js 写个最朴素的接口
import express from "express";
const app = express();
const PORT = 3000;
// GET /hello?name=小明
app.get("/hello", (req, res) => {
// req.query 就是问号后面的查询参数对象
const name = req.query.name || "朋友";
res.json({ msg: `你好,${name}!欢迎来到 Express。` });
});
app.listen(PORT, () => console.log(`http://localhost:${PORT}`));
$ node app.js
http://localhost:3000
# 浏览器或 curl 访问 http://localhost:3000/hello?name=小明
路由:四种 HTTP 动词 + 两种参数
RESTful 风格里,用 HTTP 动词表达"干什么操作",用 URL 表达"对谁干":
| 动词 | 典型用途 | 例子 |
| GET | 查数据 | app.get('/users', ...) 拿用户列表。 |
| POST | 新建 | app.post('/users', ...) 新建一个用户。 |
| PUT / PATCH | 更新 | app.put('/users/:id', ...) 改某个用户。 |
| DELETE | 删除 | app.delete('/users/:id', ...) 删某个用户。 |
路径参数 :id 和查询参数 req.query 的区别
// /users/123 —— 123 是"路径里的变量",用 :id 占位
app.get("/users/:id", (req, res) => {
console.log(req.params.id); // "123" —— 路径参数从 req.params 拿
res.json({ userId: req.params.id });
});
// /search?keyword=node&page=2 —— 问号后面的从 req.query 拿
app.get("/search", (req, res) => {
console.log(req.query.keyword, req.query.page); // "node" "2"
res.json({ keyword: req.query.keyword });
});
中间件:Express 的灵魂
中间件(Middleware)是"请求到达你的业务代码之前,依次经过的一道道工序"。打个比方:请求是个快递,中间件就是快递从大门到你手上经过的保安(登记)、前台(消毒)、快递柜(扫码)。每道工序干完,调用 next() 放它走,下一道接着处理。
一个打印每个请求日志的全局中间件
// app.use 注册的中间件,每个请求都会先过它
app.use((req, res, next) => {
const time = new Date().toISOString();
console.log(`[${time}] ${req.method} ${req.url}`);
next(); // 必须调用 next(),不然请求就卡在这,浏览器一直转圈
});
装生产必装的五个中间件
① express.json():解析请求体里的 JSON。没它 req.body 就是 undefined,这是新手第一天必踩的坑。
② express.static('public'):把 public 文件夹变成静态文件目录,图片、CSS、前端页面直接对外。
③ cors:解决跨域。前端网页在 localhost:5173,接口在 localhost:3000,浏览器默认不让跨,装了 cors() 就放行。
④ morgan:自动打印漂亮的请求日志,比手写省事。
⑤ helmet:一键给响应加上一堆安全 HTTP 头,挡掉常见 Web 攻击。
把它们串起来用
import express from "express";
import cors from "cors";
import morgan from "morgan";
import helmet from "helmet";
const app = express();
app.use(helmet()); // 安全头
app.use(cors()); // 跨域放行
app.use(morgan("dev")); // 请求日志
app.use(express.json()); // 解析 JSON 请求体,必须放在路由前面!
把路由拆出去:express.Router
所有路由都写在 app.js 里,三百行后又成大泥球。Router 是"迷你版 app",按功能拆文件:
// routers/users.js —— 用户相关的路由单独放一个文件
import { Router } from "express";
const router = Router();
router.get("/", (req, res) => res.json(["张三", "李四"]));
router.get("/:id", (req, res) => res.json({ id: req.params.id }));
export default router;
// app.js 里挂载:所有 /users 开头的请求交给它管
import userRouter from "./routers/users.js";
app.use("/users", userRouter);
错误处理与 404
// 1. 所有路由都没匹配上 → 404(放在所有路由之后)
app.use((req, res) => {
res.status(404).json({ error: "接口不存在" });
});
// 2. 全局错误处理中间件:必须四个参数 (err, req, res, next)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({ error: err.message || "服务器开小差了" });
});
记
本章小结
① Express = 路由(get/post/...)+ 中间件(use)+ 响应(json/send/status)。
② 忘了 express.json() 是第一天必踩的坑,加中间件记得 next(),跨域装 cors。
③ 项目一大就用 Router 按模块拆文件,别把所有路由堆在 app.js。