Node.js Express 框架
原生 http 模块写多了会很啰嗦——路由分发、参数解析、错误处理都要自己来。Express 是 Node 最经典、最流行的 Web 框架,把这些常用能力封装成了简洁的 API。它是几乎所有 Node Web 框架(Koa、Fastify、NestJS)的鼻祖,学它就是学整个 Node Web 生态的根基。
1. 创建 Express 应用
先 npm install express,然后几行代码就能起一个比原生 http 优雅得多的服务:
// 先安装:npm install express
const express = require("express");
const app = express();
// 路由:GET /
app.get("/", (req, res) => {
res.send("首页");
});
// 路由:带路径参数
app.get("/users/:id", (req, res) => {
// req.params 拿路径参数
res.json({ id: req.params.id });
});
// 启动服务
app.listen(3000, () => {
console.log("Express 服务跑在 http://localhost:3000");
});对比原生 http 的 if/else 路由,Express 用 app.get/post/put/delete 直接把方法和路径绑定到处理函数,清晰得多。每个处理函数收到增强过的 req 和 res——多了 req.params、req.query、res.json()、res.send() 等便捷能力。
2. 路由:URL 与方法的映射
Express 的核心就是路由。一个典型的 RESTful API 长这样:
const express = require("express");
const app = express();
// 不同 HTTP 方法的路由
app.get("/users", (req, res) => { // GET 查列表
res.json([{ id: 1 }, { id: 2 }]);
});
app.post("/users", (req, res) => { // POST 新建
res.status(201).json({ ok: true });
});
app.put("/users/:id", (req, res) => { // PUT 全量更新
res.json({ updated: req.params.id });
});
app.delete("/users/:id", (req, res) => { // DELETE 删除
res.status(204).end();
});
// 查询字符串:?name=abc&page=2
app.get("/search", (req, res) => {
// req.query 拿查询参数
res.json(req.query); // { name: "abc", page: "2" }
});
// 路由参数:/users/42 → id = "42"
app.get("/users/:id", (req, res) => {
res.send("用户 ID:" + req.params.id);
});区分两个最容易混的参数来源:
- 路径参数(
req.params):URL 路径里的:id。/users/42→req.params.id === "42"。 - 查询字符串(
req.query):URL 问号后面的。/search?name=abc&page=2→req.query.name === "abc"。
还有请求体 req.body——需要中间件解析(下面讲)。
3. 中间件:Express 的精髓
理解 Express 就是理解中间件(middleware)。中间件就是一个依次接收 req、res、next 三个参数的函数,每个请求依次穿过所有匹配的中间件,你可以在任意环节插入日志、鉴权、参数校验等逻辑:
// 中间件:一个 (req, res, next) => {} 函数
// app.use 注册的中间件,每个请求都会穿过
const express = require("express");
const app = express();
// 1. 应用级中间件:所有请求都过
app.use((req, res, next) => {
console.log(req.method, req.url, new Date().toISOString());
next(); // 必须调用 next,否则请求卡住
});
// 2. 解析 JSON 请求体(内置中间件)
app.use(express.json());
// 3. 解析 URL 编码的表单数据
app.use(express.urlencoded({ extended: true }));
// 4. 只对特定路径生效
app.use("/api", (req, res, next) => {
console.log("API 请求");
next();
});
// 5. 自定义业务中间件:鉴权
function auth(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: "未登录" });
}
req.user = verifyToken(token); // 把用户信息挂到 req 上
next();
}
// 受保护的路由:中间件 + 处理函数
app.get("/profile", auth, (req, res) => {
res.json(req.user);
});几个关键认知:
- 必须调用
next():不调用,请求就一直卡着不进下一个中间件。不调用next也不res.end,请求会一直挂起。 - 中间件顺序很重要:从上到下依次匹配。鉴权中间件要放在受保护路由之前。
- 可以挂到
req上传数据:常用做法是在前面中间件里req.user = ...,后面路由直接用。 - 路由处理函数本质也是中间件,只是通常不再调
next而是直接响应。
4. 增强的请求和响应对象
Express 给 req 和 res 加了大量便捷方法,告别原生 res.writeHead 那套啰嗦写法:
// Express 给 res 加了很多便捷方法
// 返回 JSON
res.json({ ok: true, data: [] });
// 返回纯文本
res.send("hello");
// 返回 HTML
res.send("<h1>首页</h1>");
// 设置状态码
res.status(404).send("没找到");
res.status(201).json({ created: true });
// 重定向
res.redirect("/new-path");
res.redirect(301, "https://example.com"); // 永久重定向
// 设置响应头
res.set("Content-Type", "text/html");
res.set("X-Custom", "value");
// 设置 Cookie
res.cookie("token", "abc123", {
httpOnly: true,
maxAge: 86400000 // 1 天(毫秒)
});
// 链式调用
res
.status(200)
.set("X-Powered-By", "MyApp")
.json({ ok: true });这些方法让代码可读性大幅提升——res.json() 一行搞定 JSON 响应,不用手动 JSON.stringify 再设 Content-Type。
5. 路由模块化:express.Router
项目大了,所有路由堆在 app.js 里没法维护。express.Router 让你按模块拆分路由:
// express.Router:把路由按模块拆分
// 适合中大型项目,避免所有路由堆在一个文件
// ---- routes/users.js ----
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
res.json([{ id: 1 }, { id: 2 }]);
});
router.post("/", (req, res) => {
res.status(201).json(req.body);
});
router.get("/:id", (req, res) => {
res.json({ id: req.params.id });
});
module.exports = router;
// ---- app.js ----
const users = require("./routes/users");
app.use("/users", users); // 所有 /users/* 走 users 路由
app.use("/orders", require("./routes/orders"));
app.use("/products", require("./routes/products"));这种结构让不同业务模块各自管自己的路由文件,主文件只负责挂载。是 Node 后端项目的标准组织方式。
6. 错误处理
Express 有专门的"错误处理中间件"——它和普通中间件的区别是有 4 个参数(多了个 err):
// Express 的错误处理:专门的"错误中间件"
// 同步错误:throw 或 next(err) 会被错误中间件捕获
app.get("/broken", (req, res) => {
throw new Error("故意出错");
});
// 异步错误:必须 next(err) 传给错误中间件
app.get("/async-broken", async (req, res, next) => {
try {
const data = await fetchData();
res.json(data);
} catch (err) {
next(err); // 关键:必须手动传给 next
}
});
// Express 5 / async handler 自动捕获,不用 try/catch
// 错误处理中间件:4 个参数(必须有 next)
app.use((err, req, res, next) => {
console.error("出错:", err.stack);
res.status(err.status || 500).json({
error: err.message || "服务器内部错误"
});
});
// 必须放在所有路由之后注册注意几个坑:
- 异步错误不会自动捕获:在 async 函数里出错,必须
try/catch后手动next(err)。Express 5 才支持自动捕获 async 错误。 - 错误中间件必须最后注册,且必须 4 个参数齐全(即便不用 next 也要写,Express 靠参数数量识别)。
- 统一错误格式:线上 API 建议统一错误结构,例如约定好用
code和message两个字段返回错误,前端好统一处理。
7. 静态文件与模板引擎
// 静态文件托管:一行搞定
const express = require("express");
const app = express();
const path = require("path");
// 把 public 目录下的文件对外提供
app.use(express.static("public"));
// 访问 /logo.png → 返回 public/logo.png
// 访问 /css/style.css → 返回 public/css/style.css
// 多个静态目录
app.use(express.static("public"));
app.use(express.static("uploads"));
// 加虚拟前缀:访问 /static/xxx
app.use("/static", express.static("public"));
// 用绝对路径(更稳)
app.use(express.static(path.join(__dirname, "public")));
// 模板引擎:以 EJS 为例
// npm install ejs
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
app.get("/", (req, res) => {
// 渲染 views/index.ejs,注入数据
res.render("index", { title: "首页", user: "小明" });
});express.static 是托管前端静态资源(HTML/CSS/JS/图片)的标准做法,比手写 fs 读文件安全高效得多。模板引擎(EJS、Pug、Handlebars)适合做服务端渲染(SSR),现代项目更多用前后端分离 + Vue/React,模板引擎用得少了。
8. 实战:一个完整的 RESTful API
const express = require("express");
const app = express();
// 中间件
app.use(express.json()); // 解析 JSON body
app.use((req, res, next) => { // 日志
console.log(req.method, req.url);
next();
});
// 简单的内存"数据库"
let users = [
{ id: 1, name: "小明" },
{ id: 2, name: "小红" }
];
let nextId = 3;
// RESTful 路由
app.get("/users", (req, res) => {
res.json(users);
});
app.get("/users/:id", (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return res.status(404).json({ error: "用户不存在" });
res.json(user);
});
app.post("/users", (req, res) => {
const user = { id: nextId++, name: req.body.name };
users.push(user);
res.status(201).json(user);
});
// 错误处理中间件(放最后)
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: "服务器错误" });
});
app.listen(3000, () => console.log("http://localhost:3000"));这就是一个生产可用雏形——加上数据库、参数校验(joi/zod)、JWT 鉴权、单元测试,就是一个真实项目。学完这些,你已经能独立写后端 API 了。
9. Express 之外的框架选择
- Koa:Express 原班人马做的"下一代",更轻、基于 async 的"洋葱模型"中间件。没有自带路由,要自己配。
- Fastify:主打性能,比 Express 快 2-3 倍,内置 JSON Schema 校验。对性能敏感的项目推荐。
- NestJS:面向大型企业项目,类似 Angular 的依赖注入、装饰器、模块化结构,强 TypeScript。复杂业务的首选。
- Next.js / Nuxt.js:全栈框架,前后端一体,内置 SSR。做产品推荐。
学 Express 是基础——它的中间件思想是所有这些框架的共同语言。先 Express 入门,再按需选其他。
小结
Express 是 Node Web 开发的入门必修。记住核心:路由(app.get/post) + 中间件(app.use,next 传递控制权) + 错误中间件(4 参数,放最后)。掌握它,你已经能写出真实可用的后端 API。下一篇讲调试——代码出 bug 怎么排查。
← 上一篇 Node.js 包管理
下一篇 Node.js 调试 →