# Express
30 秒速记
- 核心就两件事:路由(方法 + 路径 → 处理函数)和中间件(按注册顺序串起来的函数链)
- 取参三处:路径参数
req.params、查询串req.query、请求体req.body(要先挂解析中间件) - 宽的中间件和通配路由会吞掉后面的请求,具体的放前面,兜底的放最后
- 外部输入一律当字符串看,进业务前做校验和类型转换
Express 4里async处理器抛错不会自动进错误处理,Express 5才会
Express 就是一个轻量的路由加中间件框架,理解了「按注册顺序过一条函数链」,大部分问题都能推出来。 比如为什么 404 兜底要写最后、为什么跨域中间件要写最前、为什么 req.body 是 undefined(解析中间件没挂或挂晚了)。取参记住三个位置就够了:req.params、req.query、req.body,它们全是客户端给的,?id=abc 进来就是字符串 'abc',不校验就直接查库迟早出事。版本上要留意,Express 5 才会把 async 函数里的异常自动交给错误处理,4 里要自己 try/catch 再 next(err)。
理解「Express」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
Express 的设计非常薄,核心只有三样东西:app、中间件、路由。app 本身就是一个函数,签名是 (req, res),所以可以直接丢给 http.createServer(app)。中间件是 (req, res, next) => {} 形式的函数,app.use() 和 app.get() 这类方法都是往内部的一个数组里追加「层」,请求进来就从头到尾挨个匹配。
const express = require('express')
const app = express()
app.use((req, res, next) => { // 1. 所有请求都先经过这里
console.log(req.method, req.url)
next() // 不调就会挂住
})
app.use(express.json()) // 2. 解析 JSON 请求体,之后 req.body 才有值
app.get('/users/:id', (req, res) => { // 3. 只处理 GET /users/xxx
res.json({ id: req.params.id, tab: req.query.tab })
})
app.use((req, res) => res.status(404).json({ msg: 'Not Found' })) // 4. 兜底
app.use((err, req, res, next) => res.status(500).json({ msg: err.message })) // 5. 错误处理
app.listen(3000)
// GET /users/7?tab=post → {"id":"7","tab":"post"},注意 id 是字符串 "7"
这段代码基本覆盖了日常 Express 的全部骨架。顺序有讲究:日志在最前面才能记到所有请求;express.json() 要在用到 req.body 的路由之前;404 必须在所有路由之后,否则会把正常请求截走;错误处理放最后,而且必须写满四个参数。
Express 4.16 起,express.json() 和 express.urlencoded() 已经内置,不用再单独装 body-parser。Express 5 在 2024 年正式发布,主要变化是 async 处理器的异常会自动交给错误处理、路由路径语法更严格、移除了 res.send(200) 这类老签名,老项目升级前要过一遍迁移说明。
面试官追问
追问 1Express 4 里 async 路由查库抛错,请求直接挂住,为什么?
Express 4 不认识 Promise,async 函数里抛的错变成了未处理的 rejection,没人调 next(err)。要么每个处理器 try/catch 后 next(err),要么用 express-async-errors 打补丁,要么升到 Express 5,它会自动把 rejected 的 Promise 交给错误处理。
追问 2/users/:id 和 /users/me 两个路由,访问 /users/me 进了第一个,怎么办?
:id 能匹配任何一段,先注册就先命中,req.params.id 变成 'me'。把 /users/me 写在前面,或者给参数加约束;Express 4 可以写 /users/:id(\\d+),Express 5 换了路由语法,不再支持这种正则写法,要在处理器里自己校验。
追问 3req.query.page 拿来直接 page * 10 算分页,有什么坑?
它是字符串,'2' * 10 恰好能隐式转成 20,但 '2' + 1 就是 '21',?page=abc 会算出 NaN,?page[]=1 甚至会变成数组。进业务前统一转换:const page = Math.max(1, Number.parseInt(req.query.page, 10) || 1),复杂的用 zod 一类的 schema 校验。
追问 4一个请求进来,Express 内部大概是怎么走的?
app 收到请求后遍历内部的 stack,按顺序拿每一层去匹配路径和方法,匹配上就调用,调用里的 next() 就是「接着往后找下一层」。传了错误就跳过普通层,只找四参数的错误处理层,全部找完都没人响应就走默认的 404。
# Express 基础应用
30 秒速记
Express是第三方包,要npm install express并写进package.json,不是Node.js自带的- 定位类似前端的
jQuery:把原生http模块包一层,让写服务端更省事 - 原生要自己解析
URL、拼响应头、判断方法;Express给了路由、中间件、res.send/res.json - 它很「薄」,不管目录结构、不带
ORM,大项目常在它上面再搭一层或换NestJS/Egg
Express 是 Node.js 生态里最老牌的 Web 框架,作用就像 jQuery 之于 DOM:底层能力还是 http 模块,它只是让常见操作变短。 用原生写一个带两个路由的服务,你得自己 if (req.url === ...)、自己设 Content-Type、自己 JSON.stringify;换成 Express,app.get('/', ...) 加 res.json(obj) 两行就完了。它的优点是简单、生态大,缺点也是太简单,项目结构、参数校验、依赖注入都得自己定,所以团队项目里我会先约定好目录和中间件,不然很快就乱。
Express 是一个第三方模块,对原生模块封装了一套更灵活、更简洁的应用框架,其在 Node.js 环境的地位和作用好比 jQuery 在前端的地位和作用。
理解「Express 基础应用」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1新人说 Express 是 Node.js 内置的,就没写进 package.json,有什么问题?
Express 是第三方包,没声明依赖,换台机器或者 CI 里 npm ci 一跑,启动就报 Cannot find module 'express'。他本机能跑,多半是上级目录或全局恰好装过。依赖必须写进 dependencies。
追问 2原生 http 写一个返回 JSON 的接口,和 Express 比差在哪?
原生要写 res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)),路由靠 if 判断 req.url 和 req.method。Express 一句 res.json(data) 搞定,路由、查询串解析、404 都有现成的。
追问 3CI 报找不到 express,同事说全局装一下就好,你同意吗?
不同意。require 默认不会去全局目录找包,全局装了也不一定能解决,就算碰巧好了也是把环境差异藏起来了。正确做法是把它写进项目依赖,提交 lock 文件,CI 用 npm ci 装。
追问 4新项目还会选 Express 吗?
看规模。小服务、BFF、脚本型接口用 Express 足够,资料多上手快。团队大、要强约定和分层,就考虑 NestJS;追求性能和 schema 校验一体化,可以看 Fastify。
# 路由
30 秒速记
- 路由 = 把「请求方法 +
