Express.js 中间件速查表 - Express 常用命令与 API 开发大全

这套速查给用 Node 写 REST API 或 Web 应用的开发者,从小型内部服务到带 EJS 页面的完整站点都覆盖。内容围绕 app/router 搭建、带 :id 和查询串的路由、读懂 req 与拼好 res,再到 json/static/cors/helmet/morgan/compression 这一串中间件、EJS 渲染、JWT 鉴权,以及 async 错误处理那套写法。与随手抄的 Express 片段不同,这里按请求从进来到返回的生命周期分层排布。读完应能搭起应用、按正确顺序挂中间件和路由,并把异步错误收敛到统一 handler,而不是到处 try/catch。

编程语言·共 45 条命令·最后更新 2026-07-21
expressnodeweb

典型使用场景

面向用 Node.js 写 HTTP 服务的开发者,尤其是需要从零搭起一个带鉴权与 JSON 接口的后端时。本表的价值在于把 Express 最容易踩坑的顺序问题一次讲清:为什么 express.json() 必须写在路由注册之前、为什么错误处理中间件必须放最后且恰好四个参数、异步 handler 里抛错为什么默认不会进错误中间件。此外还覆盖了 Router 拆模块、JWT 签发校验、模板引擎挂载这一条从空项目到可上线接口的完整链路,配套的 wrap 包装器与 res.status().json() 链式写法可以直接抄进工程。

应用创建 5

const app = express()
创建应用实例
app.use(express.json())
挂载全局中间件(解析 JSON 请求体)
app.listen(3000, () => {})
监听端口启动服务
const router = express.Router()
创建可挂载路由器
app.set("port", process.env.PORT || 3000)
设置应用配置

路由定义 7

app.get("/users", handler)
注册 GET /users 路由,匹配读操作,handler 收到请求后返回数据
app.post("/users", handler)
注册 POST /users 路由,通常用于创建资源,需配合 express.json() 读 body
app.put("/users/:id", handler)
注册 PUT 路由,:id 是路径参数,在 handler 里用 req.params.id 拿到并更新
app.delete("/users/:id", handler)
注册 DELETE 路由,按路径参数定位并删除资源
app.all("/api/*", handler)
通配所有 HTTP 方法的前缀路由,适合做鉴权等通用逻辑
app.use("/api", router)
把子路由模块挂到 /api 前缀,拆分大型应用的路由组织
router.route("/users").get(list).post(create)
链式定义同一路径的多个方法,代码更聚合紧凑

请求对象 Request 6

req.params.id
取 URL 路径中的 :id 参数值,如 /users/7 得 7
req.query.page
取查询字符串参数,如 ?page=2 得 "2",分页/筛选常用
req.body.name
取请求体字段,需先 app.use(express.json()) 解析 JSON body
req.headers["content-type"]
读取请求头,判断内容类型/携带的 token 等
req.cookies.token
读取 Cookie,需先挂 cookie-parser 中间件
req.get("User-Agent")
获取指定请求头

响应对象 Response 6

res.send("hello")
发送默认 Content-Type:text/html 的文本响应,结束本次请求
res.json({ data })
把对象序列化为 JSON 并以 application/json 返回,写 API 最常用
res.status(201).json({ ok: true })
先设置状态码 201(已创建)再返回 JSON,链式一步到位
res.redirect("/login")
发送 302 跳转响应,未登录等鉴权流程常用
res.sendFile(path.join(__dirname, "index.html"))
直接把磁盘文件流式返回给客户端,静态页或一键下载用
res.set("X-Custom", "value")
设置单个响应头,如缓存策略或自定义标记

常用中间件 8

app.use((req, res, next) => { next() })
自定义中间件:先做日志/计时等处理,再 next() 交给下一个
express.urlencoded({ extended: true })
解析 HTML 表单的 application/x-www-form-urlencoded 请求体到 req.body
app.use("/static", express.static("public"))
把 public 目录以 /static 前缀对外提供静态资源,不用逐个写路由
cors()
跨域中间件,给响应加 CORS 头,前后端分离时按需配置来源白名单
helmet()
安全中间件,批量设置 X-Content-Type-Options 等 HTTP 安全头
morgan("dev")
请求日志中间件,dev 格式输出精简的一行摘要,开发排查很直观
cookie-parser()
把请求 Cookie 解析进 req.cookies,之后才能读 cookie 值
compression()
响应压缩中间件,对大 JSON/文本响应显著减体积省带宽

模板引擎与 JWT 6

app.set("view engine", "ejs")
设置默认模板引擎为 EJS,配合 layout/渲染使用
app.set("views", path.join(__dirname, "views"))
指定模板文件所在目录,res.render 按其相对路径找模板
res.render("index", { title: "Home" })
渲染把数据传入 index 模板并输出 HTML 响应,服务端渲染页面用
jwt.sign(payload, secret, { expiresIn: "1h" })
用 secret 签发带 1 小时过期时间的 JWT,登录成功时生成
jwt.verify(token, secret)
校验并解析 token,签名不符或过期会抛错,鉴权中间件核心
req.headers.authorization
从 Authorization 头取 "Bearer <token>",按约定解析出 JWT 明文

错误处理与实用模式 7

const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(next)
异步错误包装器,自动捕获 async 错误
app.use((req, res) => res.status(404).send("Not Found"))
404 兜底处理
app.use((err, req, res, next) => res.status(500).json({ error: err.message }))
全局错误处理中间件(必须 4 个参数)
next(err)
传递错误到错误处理中间件
process.on("uncaughtException", handler)
捕获未处理异常
process.on("unhandledRejection", handler)
捕获未处理 Promise 拒绝
module.exports = router
导出路由模块供 app.use 挂载

参数矩阵

参数作用示例
express()创建应用实例,后续所有设置都挂在它上面const app = express()
express.json()解析 JSON 请求体到 req.body,必须先于路由注册app.use(express.json())
express.urlencoded({ extended: true })解析 HTML 表单请求体到 req.bodyapp.use(express.urlencoded({ extended: true }))
app.use(path, mw)把中间件或子路由挂到指定前缀,所有匹配请求都会经过app.use("/api", router)
app.use(express.static("public"))对外提供静态目录,不用逐个写路由app.use("/static", express.static("public"))
app.set(key, value)设置应用级配置,如端口、模板引擎、模板目录app.set("views", path.join(__dirname, "views"))
app.listen(port, cb)监听端口启动服务app.listen(3000, () => {})
express.Router()创建可独立挂载的路由器,便于按模块拆分const router = express.Router()
router.route(path).get().post()在一条链上聚合定义同一路径的多个方法router.route("/users").get(list).post(create)
app.all("/api/*", mw)通配所有 HTTP 方法,常用于鉴权等通用前置逻辑app.all("/api/*", auth)
req.params.id读取路径参数 :id 的值app.put("/users/:id", handler)
req.query.page读取查询字符串参数(值是字符串)const page = req.query.page
req.get("User-Agent")按名读取请求头const ua = req.get("User-Agent")
req.cookies.token读取 Cookie,需先挂 cookie-parserapp.use(cookieParser())
res.send(str)发送文本响应并结束本次请求res.send("hello")
res.json(obj)序列化为 application/json 返回,写 API 最常用res.json({ data })
res.status(201).json(obj)先设状态码再返回 JSON,链式一步到位res.status(201).json({ ok: true })
res.redirect(path)发送 302 跳转响应res.redirect("/login")
res.sendFile(absPath)把磁盘文件流式返回给客户端res.sendFile(path.join(__dirname, "index.html"))
res.set(key, value)设置单个响应头res.set("X-Custom", "value")
res.render(view, data)渲染模板并输出 HTML 响应res.render("index", { title: "Home" })
helmet()批量设置 X-Content-Type-Options 等 HTTP 安全头app.use(helmet())
morgan("dev")精简单行请求日志,开发排查直观app.use(morgan("dev"))
cors()加跨域响应头,前后端分离时按需配来源白名单app.use(cors())
compression()压缩响应体,大 JSON/文本显著减体积app.use(compression())
jwt.sign(payload, secret, { expiresIn })签发带过期时间的 JWTjwt.sign(payload, secret, { expiresIn: "1h" })
jwt.verify(token, secret)校验并解析 token,签名不符或过期会抛错const data = jwt.verify(token, secret)
req.headers.authorization取 Bearer 形式的 token 原文const token = req.headers.authorization
next(err)把错误交给后续错误处理中间件next(err)
app.use((err, req, res, next) => ...)全局错误处理中间件,必须四个参数且放最后app.use((err, req, res, next) => res.status(500).json({ error: err.message }))
module.exports = router导出路由模块供 app.use 挂载module.exports = router

易错点与避坑指南

现象POST 请求里 req.body 是 undefined,访问 req.body.name 直接抛 TypeError。

原因express.json() 挂载在路由注册之后,或根本忘了挂载,body 解析器没有生效。

处置把所有 app.use(express.json()) 提到所有 app.get/app.post 之前;表单提交则补 app.use(express.urlencoded({ extended: true }))。

现象异步 handler 里 throw new Error 之后请求一直挂着,客户端超时而不是返回 500。

原因Express 4 不会自动捕获 async 函数返回的 rejected Promise,错误没被 next() 转交。

处置用 wrap 包装器把 async handler 包一层:const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(next),或升级到会自动捕获的版本并保持 handler 统一走包装。

现象自定义的错误处理中间件写了四个参数却完全没被调用,错误直接冒泡到默认处理器。

原因中间件被注册在了路由之前,或者用 app.use 挂载时顺序放在了错误发生点之前,Express 按注册顺序匹配。

处置把 (err, req, res, next) 四参数中间件放到所有路由和 404 兜底之后,作为最后一段 app.use。

现象不传任何参数的中间件不被 Express 识别,函数体完全不执行。

原因Express 靠函数参数个数(length === 4)识别错误处理中间件,箭头函数写成一个参数就被当普通中间件。

处置显式写出四个形参:(err, req, res, next) => { ... },即使 next 用不到也要留着。

现象重启后进程仍然占着 3000 端口,报 EADDRINUSE。

原因脚本里抛出的异常没有被处理,Node 进程因未捕获异常退不干净,端口未释放。

处置补上 process.on("uncaughtException", handler) 与 process.on("unhandledRejection", handler) 做收尾退出,再确认没有遗留的 node 进程占用端口。

现象读 req.cookies.token 得到 undefined,读请求头里的 token 却是好的。

原因没有挂 cookie-parser 中间件,Express 本身不解析 Cookie。

处置加 app.use(cookieParser()),且必须放在任何会读 req.cookies 的路由之前。

现象静态资源请求全部 404,目录明明存在。

原因express.static 的参数是相对进程工作目录的路径,用相对路径时进程 cwd 与预期不一致就找不到目录。

处置改用绝对路径:express.static(path.join(__dirname, "public")),避免依赖启动目录。

排障路径

  1. 1确认中间件注册顺序(body 解析器必须在路由前)

    grep -n "app.use\|router\." server.js

    输出中 express.json() 的行号应小于任何 app.get/app.post 的行号,否则 req.body 会是 undefined。

  2. 2确认错误处理中间件参数个数并位于末尾

    grep -n "err, req, res, next" server.js

    应只匹配到一处,且该 app.use 出现在文件所有路由定义之后。

  3. 3打开精简请求日志观察实际命中了哪些路由

    app.use(morgan("dev")); app.listen(3000, () => {})

    终端每请求输出一行摘要,方法、URL、状态码一目了然,可直接判断是否落到 404 兜底。

  4. 4定位进程级未捕获异常与 Promise 拒绝

    node --trace-uncaught server.js

    配合 process.on("uncaughtException", handler) 与 process.on("unhandledRejection", handler) 打印完整堆栈,定位是哪个请求链路漏了 next(err)。

  5. 5打印请求体与请求头确认解析链路

    app.use((req, res, next) => { console.log(req.body, req.headers["content-type"], req.get("User-Agent")); next() })

    中间件里必须调用 next(),否则请求会停在这一层。

  6. 6校验 JWT 是否有效

    const data = jwt.verify(token, secret)

    签名不符或已过期会抛错,鉴权中间件里用 try/catch 捕获后调用 next(err) 或 res.status(401).json({ error: "unauthorized" })。

命令示例

最小可用 API:JSON 解析 + 路由 + 201 响应

const express = require("express") const app = express() app.use(express.json()) app.get("/users/:id", (req, res) => { res.json({ id: req.params.id }) }) app.post("/users", (req, res) => { res.status(201).json({ ok: true, name: req.body.name }) }) app.listen(3000, () => console.log("up"))

express.json() 必须挂在路由之前,否则 POST 的 req.body 是空对象;GET 里用 req.params.id、POST 里用 req.body.name,分别对应表内的路径参数与请求体取值。

拆分子路由挂到 /api 前缀,同一路径链式定义多方法

// routes/users.js const router = express.Router() router.route("/users") .get((req, res) => res.json({ page: req.query.page })) .post((req, res) => res.status(201).json(req.body)) module.exports = router // app.js app.use(express.json()) app.use("/api", router)

router.route 把同一路径的多个方法收在一处,比散落的 app.get/app.post 更难漏改;子模块只导出 router,由主应用决定前缀,换前缀时不必进子文件。

async 错误包装器 + 全局错误中间件

const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(next) app.get("/boom", wrap(async (req, res) => { throw new Error("upstream failed") })) app.use((err, req, res, next) => { if (res.headersSent) return next(err) res.status(500).json({ error: err.message }) })

Express 不会自动捕获 async 函数返回的 rejected Promise,必须用 wrap 把 catch 接到 next(err) 上,否则请求既不成功也不返回,客户端一直挂到超时。

JWT 登录 + 前缀路由统一鉴权

app.post("/login", (req, res) => { const token = jwt.sign({ sub: req.body.name }, secret, { expiresIn: "1h" }) res.json({ token }) }) app.use("/api", (req, res, next) => { try { req.user = jwt.verify(req.headers.authorization.replace("Bearer ", ""), secret) next() } catch (e) { res.status(401).json({ error: "unauthorized" }) } })

签发用 jwt.sign(..., { expiresIn }) 定生命周期,鉴权用 jwt.verify 校验签名与过期。把中间件挂在 /api 前缀上,所有子路由自动受保护,不必逐个路由包一层。

常见坑与注意事项

  • 错误处理中间件必须是 (err, req, res, next) 四个参数:Express 按参数个数识别它,写成 (err, req, res) 会被当普通中间件,线上错误全部静默变成请求挂起。
  • app.use 的执行顺序即匹配顺序:404 兜底、express.static 或错误中间件一旦写在业务路由之前,所有请求都会被它先截走,表现为「接口全部 404」且日志无记录。
  • express.json() 默认只接受 application/json 且请求体上限约 100kb;表单和文件上传用错解析器会拿到空 body,上传接口则直接被拒,需要 urlencoded 或专门的 multipart 中间件。
  • req.params.id、req.query.page 全是外部输入,直接拼进 SQL 或文件路径就是注入与目录穿越;至少要做类型/白名单校验,否则一个请求就能拖库或读任意文件。
  • res.send() / res.json() 之后再写内容或再次响应会抛 Cannot set headers after they are sent,日志里只看到这个报错,真正的首个异常常在更早的 await 上。
  • jwt.verify 抛错默认会把完整 token 与签名细节带进错误信息;直接 res.json({ error: e.message }) 回给客户端会泄露密钥材料,必须只返回 401 这类固定文案。

提示

  • 中间件顺序很重要,body-parser 必须在路由之前,错误处理中间件必须放最后。
  • 错误处理中间件必须 4 个参数 (err, req, res, next),否则 Express 不识别。
  • 生产环境用 helmet() 加安全头,cors() 按需配置白名单。
  • express.static 可设置多个目录,按顺序查找文件。
  • Router 可模块化路由,app.use("/api", router) 挂载子路由。

常见问题

中间件顺序到底怎么排?为什么改完就报 req.body 是 undefined?

req.body 为空几乎都是顺序问题。app.use(express.json()) 必须挂在所有读取请求体的路由之前,否则路由先命中并执行,body 还没被解析。同理 req.cookies.token 需要先 cookie-parser();表单提交要 app.use(express.urlencoded({ extended: true })) 才能解析 application/x-www-form-urlencoded。另一头,错误处理中间件必须放在所有路由之后,且必须写满四个参数 (err, req, res, next),否则 Express 不识别它,async 错误会变成进程挂起。正确的整体顺序是:解析类 → 日志/安全类(morgan、helmet、cors、compression)→ 路由 → 404 兜底 → 错误处理。

async 路由里的异常为什么没进错误中间件?

Express 4 不会自动捕获 async 函数返回的 rejected Promise,必须靠两种方式之一。第一种是包装器:const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(next),把 rejection 交给 next(err),再由全局错误处理中间件 (err, req, res, next) => res.status(500).json({ error: err.message }) 收口,路由写成 app.post("/users", wrap(async (req, res) => { ... }))。第二种是显式 try/catch 后调 next(err)。校验失败之类不该算 500 的错误,可以在错误中间件里按错误类型映射状态码。进程级兜底再用 process.on("uncaughtException", handler) 和 process.on("unhandledRejection", handler)。

req.params、query、body、headers、cookies 分别从哪来?

五个取值口对应请求的五个部位。req.params.id 来自 URL 路径里的 :id 占位符,注册 app.put("/users/:id", handler) 后在 /users/7 上得到 "7"。req.query.page 来自查询字符串 ?page=2,值是字符串,分页筛选常用。req.body.name 来自请求体,必须先 express.json() 或 urlencoded() 解析。req.headers["content-type"] 读请求头判断内容类型或携带的 token,也可以用更顺手的 req.get("User-Agent")。req.cookies.token 读 Cookie,需要 cookie-parser()。取认证 token 的常见做法是从 req.headers.authorization 剥出 "Bearer <token>" 再 jwt.verify(token, secret)。

res.send、res.json、sendFile、redirect 该怎么选?

按「返回什么」选。res.send("hello") 发的是默认 Content-Type: text/html 的文本,结束本次请求;res.json({ data }) 把对象序列化为 JSON 并以 application/json 返回,写 API 最常用。创建资源时链式写 res.status(201).json({ ok: true }),先设状态码再返回,一步到位。res.redirect("/login") 发 302 跳转,未登录等鉴权流程用。res.sendFile(path.join(__dirname, "index.html")) 直接把磁盘文件流式返回,静态页或一键下载用。自定义响应头用 res.set("X-Custom", "value"),比如设置缓存策略。服务端渲染页面则用 res.render("index", { title: "Home" }),它按 app.set("views", ...) 指定的目录找模板。

路由多了怎么组织,中间件各自解决什么问题?

用 Router 拆模块:const router = express.Router() 定义子路由,app.use("/api", router) 挂到前缀,router.route("/users").get(list).post(create) 链式定义同一路径的多个方法,最后 module.exports = router 导出供入口挂载;app.all("/api/*", handler) 适合做鉴权这类对所有方法生效的前缀逻辑。中间件各司其职:morgan("dev") 输出一行精简请求摘要便于开发排查,helmet() 批量设置 X-Content-Type-Options 等安全头,cors() 处理跨域并可按白名单配来源,compression() 压缩大 JSON/文本响应省带宽,express.static("public") 把目录以 /static 前缀对外提供静态资源,不用逐个写路由。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们