主题
Express 与常用命令
本页对应 Express 与常用命令,共 3 段视频,约 33 分钟。
学习目标
- 能准确区分 Node.js 中模块与包的概念,说明 CommonJS 与 ES Module 的默认导出/导入、命名导出/导入语法,并根据项目环境选择合适规范。
- 能独立使用
npm init -y、npm i、npm i -g、npm uninstall等命令完成项目初始化、依赖安装与卸载,并解释package.json和node_modules的作用。 - 能基于 Express 创建 Web 服务对象,监听 GET 等请求方法与资源路径,使用
res.send()返回响应,并用app.all('*')统一处理 404 状态码。 - 能独立开发
GET /api/province接口:使用fs.readFile()读取province.json,通过绝对路径拼接定位文件,并将数据响应给请求方后在浏览器验证结果。 - 能解释浏览器同源策略的判定条件,识别因协议、域名或端口不一致导致的跨域问题,并说明常见跨域解决思路。
知识点
一、模块化:每个文件都是一个独立模块
为什么需要:把代码都写在一个文件里,变量容易互相污染,功能也不好复用。Node.js 里把每个文件看成一个模块,每个模块有独立作用域,按需加载,再通过导出和导入让外部使用。
怎么想:先记住一句话:文件叫模块,模块默认是“私有的”。别人想用我的变量或方法,必须我导出、别人导入。Node.js 里常见两大类标准:CommonJS 和 ECMAScript。视频里也提醒:Node.js 项目环境一般用 CommonJS;前端工程化项目更常用 ECMAScript 标准。
怎么做:CommonJS 常用 module.exports 导出,用 require 导入;ECMAScript 分默认导出导入和命名导出导入。默认导出用 export default,导入时直接用一个变量接收;命名导出用 export 修饰变量定义,导入时用 import { 同名变量 } from '模块路径'。如果希望按需导入,命名方式更合适,因为需要哪几个变量就导入哪几个。


js
// CommonJS 语法示意
// a.js
module.exports = { name: 'tom' }
// b.js
const obj = require('./a')
// ECMAScript 默认导出导入示意
// a.js
export default { name: 'tom' }
// b.js
import obj from './a.js'
// ECMAScript 命名导出导入示意
// a.js
export const name = 'tom'
// b.js
import { name } from './a.js'易错点:不要把“文件作用域”当成“全局作用域”,没导出就不能直接用;默认导出导入的是一个大对象,导入时不要硬套大括号;命名导入要写同名变量和大括号;CommonJS 与 ECMAScript 标准不要在同一套写法里混着用。视频里还强调,CommonJS 导出语法有很多种,初学重点掌握常用的 module.exports 即可。
二、包:把模块、代码和资料聚合成文件夹
为什么需要:项目变大后,光有单个模块不够,需要把模块文件、代码文件和其他资料组织到一个文件夹里,这就是包。包让项目结构更清晰,也方便工具和方法被复用。
怎么想:先区分“文件”和“文件夹”:文件是模块,文件夹是包。包又分项目包和软件包。项目包写项目需求和业务逻辑;软件包封装工具、方法、属性,一般用 npm 管理。软件包再分本地软件包和全局软件包:本地软件包作用在当前项目,供项目调用;全局软件包作用在所有项目,一般封装命令或工具,支撑项目运行。
怎么做:写业务逻辑时建项目包;需要工具能力时用 npm 安装本地软件包;需要全局命令工具时安装全局软件包。视频里举例:本地软件包如项目里引入使用的工具库;全局软件包如 nodemon,它能监测代码变化并自动重启程序。

易错点:不要把项目包和软件包混为一谈;本地软件包不是“只能在本地电脑用”,而是“作用在当前项目”;全局软件包也不是所有项目代码都直接 require,它更多提供命令工具。安装前要确认是当前项目需要,还是整台电脑多个项目都需要。
三、常用命令:执行文件、初始化、装包与卸包
为什么需要:Node.js 开发离不开终端命令。会执行 JS 文件、生成项目清单、安装和卸载软件包,才能把模块化和包真正用起来。
怎么想:视频里回忆命令时,是先从需求倒推:执行 JS 文件用 node;想自动监测重启可用全局软件包 nodemon;项目没有 package.json 就用 npm init -y 生成;下载本地软件包用 npm i 包名;下载全局软件包加 -g;卸载用 npm uninstall 包名,卸载全局再加 -g。
怎么做:执行文件:node 文件名,也可用 nodemon 文件名 替代。初始化项目清单:npm init -y,-y 表示配置项都用默认值。安装本地软件包:npm i 软件包名,源码会放在当前项目的 node_modules 文件夹中。安装全局软件包:npm i 软件包名 -g。卸载本地包:npm uninstall 软件包名;卸载全局包:npm uninstall 软件包名 -g。

易错点:用 npm init -y 时,所在文件夹名字不要有特殊符号和中文,建议用英文小写和数字;-g 表示全局安装,不要漏写或乱写位置;下载后要检查 package.json 是否记录依赖、node_modules 是否出现对应包;卸载时本地包和全局包命令差一个 -g,不要搞混。
四、小结自测
安装本地软件包的命令是:npm i 软件包名。把模块化、包的概念和这些常用命令连起来记:文件是模块,文件夹是包;模块靠导出导入使用,包靠 npm 管理;项目清单、装包、卸包、执行文件,是后续 Node.js 开发最基础的一套操作。
本节目标与 Express 是什么
为什么需要:只用 Node.js 内置 http 模块写 Web 服务时,要自己判断请求方法、请求路径、设置响应头,代码容易重复。Express 是基于 Node.js 平台、快速、开放、极简的 Web 开发框架,底层基于 http 模块封装,可以更简单地开发 Web 服务、提供数据接口和网页资源。

怎么想:把 Express 理解成一个本地软件包。先安装到项目里,再导入并调用它,得到 Web 服务对象,然后用这个对象监听请求方法和路径。
怎么做:本节需求是:监听 get 请求方法和 / 路径,有人请求时返回一段提示字符串;其他请求方法和路径默认返回 404 提示;最后监听端口启动服务。
易错点:Express 不是 Node.js 内置模块,必须先通过 npm 下载到当前项目中;不要把它和 http 模块混用成同一套写法。
准备项目并安装 Express
为什么需要:Express 是本地软件包,项目里要先有 package.json 记录依赖,再把包下载到 node_modules 中。
怎么想:转写稿中演示的顺序是:先初始化项目清单,再安装 Express。npm init -y 会生成默认配置的 package.json;npm i express 会把 Express 及其依赖下载到当前项目。
怎么做:在项目文件夹终端执行:
bash
npm init -y
npm i express转写稿提到安装成功时会看到下载了多个依赖包,node_modules 里会出现 Express 及其他依赖包。
易错点:使用 npm init -y 时,所在文件夹名不要包含中文和特殊符号,建议用英文小写和数字组成;安装的是本地软件包,不要误加 -g。
创建 Web 服务对象
为什么需要:后续监听请求、返回响应、启动端口,都要通过 Express 创建出的 Web 服务对象完成。
怎么想:require('express') 导入 Express 后,得到的是一个函数;调用这个函数,就得到一个 Web 服务对象。
怎么做:
js
const express = require('express')
const server = express()易错点:不要写成 const server = require('express') 后直接 server.get,因为导入的是函数,需要先调用 express() 创建服务对象。
监听 GET / 并返回提示
为什么需要:Web 服务要能根据“请求方法 + 请求路径”匹配请求,并返回响应内容。
怎么想:Express 的服务对象上可以直接调用与请求方法同名的方法,例如 server.get 监听 GET 请求。第一个参数是请求路径,第二个参数是命中后的回调函数,回调中 req 是请求对象,res 是响应对象。
怎么做:监听根路径 /,用 res.send 返回字符串。转写稿说明,res.send 比原生写法更方便,Express 会帮助处理响应头,返回中文时不需要自己手动设置。
js
server.get('/', (req, res) => {
res.send('你好,欢迎使用 Express')
})易错点:浏览器地址栏输入地址后回车,默认发起的是 GET 请求;如果要测试 POST、PUT、DELETE 等方法,地址栏不方便,需要借助接口测试工具。路径要写准确,/ 和 /api/province 是不同的匹配。
用 all('*') 兜底 404
为什么需要:上面的接口没有匹配到时,服务不能没有响应,应统一告诉请求方资源不存在。
怎么想:server.all 表示监听任意请求方法,'*' 是通配符,表示任意路径。它放在具体接口后面,起到兜底作用:前面没有匹配的请求,会落到这里。
怎么做:先设置 404 状态码,再返回提示内容:
js
server.all('*', (req, res) => {
res.status(404)
res.send('你要访问的资源路径不存在')
})易错点:server.all('*') 要放在具体路由之后,否则可能提前匹配所有请求;res.status(404) 设置的是响应状态码,res.send 返回的是响应体内容,两者不是一回事。
启动服务与浏览器调试
为什么需要:Web 服务只有监听端口后,浏览器才能通过本机地址访问。
怎么想:用 server.listen 监听端口号,例如 3000;启动成功后执行回调,在终端打印提示。转写稿中使用 nodemon server.js 启动,便于代码变化后自动重启。
怎么做:完整代码如下:
js
/**
* 目标:基于 express 本地软件包,开发 Web 服务,返回资源给请求方
*/
// 1. 下载 express 软件包
// 2. 导入并创建 Web 服务对象
const express = require('express')
const server = express()
// 3. 监听请求的方法和请求的资源路径
server.get('/', (req, res) => {
res.send('你好,欢迎使用 Express')
})
// 4. 监听任意请求的方法和请求的资源路径
server.all('*', (req, res) => {
res.status(404)
res.send('你要访问的资源路径不存在')
})
// 5. 监听端口号,启动 Web 服务
server.listen(3000, () => {
console.log('Web 服务已启动')
})终端执行:
bash
nodemon server.js浏览器访问 http://localhost:3000/,应看到“你好,欢迎使用 Express”;再访问如 http://localhost:3000/api/123,应看到“你要访问的资源路径不存在”。打开 F12 的“网络”面板刷新请求,可以看到该请求响应状态码为 404。
易错点:端口号被占用时服务会启动失败,可换端口;请求路径写错会进入 all('*') 兜底;nodemon 是全局软件包命令,若本机没有安装,应使用 node server.js 启动。
目标:用 Express 提供省份列表接口
为什么需要:前端页面要展示省份列表,不能把数据写死在前端,而应由后端接口统一提供。这样数据可维护,前端只需按约定请求接口。
怎么想:本节目标很明确——基于 Express 的 Web 服务,开发一个返回省份列表数据的接口。请求方访问本机 3000 端口的 Web 服务,请求资源路径 /api/province,后端读取准备好的 province.json 并把数据返回。
怎么做:先明确接口三要素:请求方法为 GET,请求路径为 /api/province,数据来源为 data/province.json。
易错点:不要把“页面地址”和“接口路径”混在一起;接口路径必须与前端请求路径一致,包括 /api 前缀。

从需求推导到 Express 路由
为什么需要:Express 封装了原生 HTTP 模块的请求监听方式,可以直接用与请求方法同名的方法监听路由,不必自己写大量判断。
怎么想:转写中反复强调:要监听 GET 请求,就调用 Web 服务对象的 get 方法;第一个参数是要监听的资源路径,第二个参数是命中后执行的回调函数。回调里的 req 是请求对象,res 是响应对象。
怎么做:用 server.get('/api/province', (req, res) => { ... }) 监听接口。只有请求方法为 GET 且路径匹配 /api/province 时,才进入回调。
易错点:get 是 Web 服务对象的方法,不是 req 或 res 的方法;路径不要漏写开头的 /;不要把 get 写成 post。
读取 province.json 并返回数据
为什么需要:省份数据存放在 JSON 文件中,接口需要把文件内容读出来再响应给请求方。
怎么想:读本机文件要用 fs.readFile;拼接文件路径要用绝对路径,借助 path.join(__dirname, 'data/province.json'),其中 __dirname 表示当前文件所在文件夹的绝对路径。读取成功后,用 Express 提供的 res.send 返回内容。
怎么做:在路由回调中调用 fs.readFile,把读出的 data 用 toString() 转成字符串,再交给 res.send。转写中特别提到,res.send 会帮忙设置响应头,因此返回 JSON 字符串时比原生写法更方便。
易错点:不要用普通相对路径直接读文件;注意文件名是 province.json;__dirname 拼写不要错;读取回调里的 data 是 Buffer,需要转字符串再返回。
核心代码
js
// 监听 get 请求方法,监听资源路径 /api/province,就读取 province.json 省份数据返回
server.get('/api/province', (req, res) => {
fs.readFile(path.join(__dirname, 'data/province.json'), (err, data) => {
res.send(data.toString())
})
})为什么需要:这段代码就是本节接口的核心实现,把“监听路由”和“读取文件返回”串起来。
怎么想:先由 server.get 命中接口,再在回调中读取 JSON 文件,最后用 res.send 响应。
怎么做:把这段代码补到模板的 server.js 中;模板里已经准备好了基于 Express 的 Web 服务基础代码,并引入了 fs、path 等模块。
易错点:本节核心代码没有展开错误处理,调试时若没有返回数据,要先检查终端报错、文件路径和请求地址是否一致,不要只盯浏览器。
启动服务并调试验证
为什么需要:接口写完必须启动 Web 服务并实际请求,才能确认路由、路径和文件读取都正确。
怎么想:转写中的调试流程是:在终端运行 nodemon server.js 启动 3000 端口服务,然后在浏览器输入 http://localhost:3000/api/province。浏览器地址栏默认发起 GET 请求,正好符合本接口要求。
怎么做:启动后访问该地址,浏览器中看到省份列表的 JSON 字符串数据,就说明接口返回成功。
易错点:端口要和服务监听端口一致;路径不要写成 /province 或漏掉 /api;浏览器地址栏只能方便地发起 GET,若要测试其他方法需用接口工具;看到 JSON 字符串属于正常响应结果。
小结:接口开发的固定思路
为什么需要:掌握一个案例后,要能迁移到其他接口开发。
怎么想:先确定“用什么方法请求哪个路径”,再确定“命中后做什么、返回什么”。本节就是 GET /api/province,读取 province.json 并返回。
怎么做:用 Web 服务对象调用与请求方法同名的方法,传入资源路径和回调;在回调中组织响应内容,用 res.send 返回。
易错点:Express 创建的服务对象监听请求时,不是自己写 if/else 判断方法,而是调用同名方法;路径匹配、文件路径、启动命令三者任一出错,都会导致接口调试失败。
说明
本节转写稿实际围绕 Node.js 模块化、Express 搭建 Web 服务与接口开发展开,和“浏览器的同源策略”没有直接对应内容。为避免把无关知识混入本节、也不编造转写稿中不存在的推导,下面以笔记中的“源、同源、同源策略、AJAX 限制”为主线扩写;转写稿中诸如“艾克玛 script”等表述属于 Node 模块化内容,本节不展开。
本节目标与问题引入
为什么需要: 学习同源策略,是为了理解浏览器为什么要限制页面脚本访问其他源的资源,以及为什么 AJAX 请求有时会被浏览器拦截。它不是单纯的“背概念”,而是后面排查接口请求、跨域问题的基础。
怎么想: 先抓住两个对象:当前网页加载时所在的“源”,以及 AJAX 请求目标的“源”。同源策略讨论的是这两个源之间的关系,不是服务器和服务器之间的关系。
怎么做: 学习时按“概念—作用—判定—限制”的顺序推进:同源策略是什么;它防什么;源由什么组成;什么情况算同源;它如何限制 AJAX。
易错点: 不要把“同源策略”理解成“所有请求都发不出去”。笔记强调的是限制交互和限制 AJAX 只能向同源 URL 发起请求,重点在浏览器安全机制。
为什么需要同源策略
为什么需要: 浏览器里会加载各种文档和 JS 脚本。如果没有限制,一个恶意页面可能借助用户浏览器中的身份和上下文,去和其他源的资源交互,扩大攻击面。
怎么想: 笔记给出的核心作用是:同源策略限制一个源的文档或者加载的 JS 脚本,与另外一个源的资源进行交互,帮助阻隔恶意文档,减少被攻击的媒介。
怎么做: 可以用笔记中的钓鱼网站例子理解:恶意页面收集信息后,用 AJAX 发起恶意请求,把转账信息传递到银行服务器,形成跨站伪造请求攻击。

易错点: 同源策略不是“银行服务器不安全”,而是浏览器通过限制跨源交互,降低恶意文档借用用户浏览器环境发起危险操作的风险。
什么是“源”
为什么需要: 要判断同源,必须先明确“源”的组成。否则只看域名,容易误判。
怎么想: 笔记明确:源指的是协议、域名、端口号的组成。三者共同构成一个源。
怎么做: 看到一个 URL,就拆成三部分:协议是什么,域名是什么,端口号是什么。比较两个 URL 是否同源时,三项逐一对照。

易错点: 不能只比较域名。协议不同、端口号不同,或者域名不同,都不属于同一个源。即使是同一台机器上的服务,只要访问时使用的协议、域名或端口不同,也不能直接认定为同源。
如何判断同源
为什么需要: 同源策略的触发条件取决于“网页加载时所在源”和“AJAX 请求时的源”是否一致。不会判断同源,就无法分析请求为什么被限制。
怎么想: 笔记给出的判定标准是:网页加载时所在的源,和 AJAX 请求时的源,协议、域名、端口号全部相同,即为同源。
怎么做: 排查时先看浏览器地址栏当前页面的源,再看 Network 面板中 AJAX 请求 URL 的源,逐项比较协议、域名、端口号。三项全部相同,才是同源访问。

易错点: 笔记中的钓鱼网站例子并不是同源访问,因为它们的域名不同。实际排查时也要注意:页面地址和请求地址看起来很像,不代表同源;只要有一项不同,就不能算同源。
同源策略如何限制 AJAX
为什么需要: 同源策略最终要落到 AJAX 上理解:为什么页面里的 AJAX 不能随意请求其他源的 URL。
怎么想: 笔记在小结中明确:同源策略限制的是 AJAX 只能访问同源 URL。也就是说,浏览器会保护网站安全,限制 AJAX 向非同源 URL 发起请求。
怎么做: 写 AJAX 前先确认请求目标与当前页面是否同源。若协议、域名、端口号全部相同,才符合笔记所说的同源访问条件;若不同,就要意识到这是同源策略限制的场景。
易错点: 不要以为“接口在浏览器里能打开”就等于“页面里的 AJAX 一定能正常读取”。同源策略比较的是页面所在源和 AJAX 请求源,而不是单独看接口地址是否可访问。
调试与排查思路
为什么需要: 同源问题往往表现为请求行为异常,必须用固定步骤定位,而不是凭感觉改代码。
怎么想: 先确认当前页面源,再确认请求目标源,最后比较三项是否完全一致。这个推导过程与笔记中“网页加载时所在的源”和“AJAX 请求时的源”对照判断是一致的。
怎么做: 打开浏览器开发者工具,在地址栏记录页面的协议、域名、端口;在 Network 中找到对应 AJAX 请求,记录请求 URL 的协议、域名、端口;逐项比较。若发现域名不同,就回到笔记中的钓鱼网站例子理解:域名不同即不是同源访问。
易错点: 排查时不要只看路径,比如 /api/province 这类路径相同并不代表同源;同源判断看的是协议、域名、端口号,不是资源路径。
小结
同源策略的核心是:限制一个源的文档或加载的 JS 脚本与另一个源资源交互,帮助阻隔恶意文档、减少攻击媒介;源由协议、域名、端口号组成;网页加载时的源与 AJAX 请求时的源三项全部相同才是同源;浏览器通过同源策略保护网站安全,限制 AJAX 只能向同源 URL 发起请求。掌握这些,才能正确理解笔记中的钓鱼网站案例和后续 AJAX 请求限制问题。
认识跨域:先判断“源”是否一致
为什么需要:前端页面要拿后端数据,最常用方式就是 AJAX 请求接口。但浏览器不是“只要 URL 能打开就一定能请求”,它会检查页面所在源和接口所在源是否一致。理解这一点,才能看懂控制台里那些“请求失败但接口明明能打开”的现象。
怎么想:跨域不是“请求方法写错”,也不是“后端没启动”,而是“网页文档所在源”和“AJAX 访问的源”不一致。源由协议、域名、端口三部分组成,三者有一个不同,就发生跨域访问。例如页面由 LiveServer 打开,地址是 http://localhost:5500,而 AJAX 请求 http://localhost:3000/api/province,协议相同、域名相同,但端口 5500 和 3000 不同,因此跨域。

怎么做:先记住判断顺序:看协议 → 看域名 → 看端口。笔记中的需求是:在 LiveServer 的 Web 服务启动网页,用 AJAX 访问本机 Web 服务提供的省份列表接口,体验跨域问题。页面里写如下代码:
js
// 目标:请求本机 Web 服务提供的省份列表数据
axios({
url: 'http://localhost:3000/api/province',
// method: 'get'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})易错点:不要只盯着“localhost 一样”就认为同源。localhost:5500 与 localhost:3000 端口不同,仍然跨域;也不要误以为 127.0.0.1 和 localhost 一定可以混用。判断跨域必须把协议、域名、端口三项一起看。
复现跨域报错:接口能打开,不等于页面 AJAX 能成功
为什么需要:只看概念容易模糊,必须亲手复现一次报错,才能把“跨域”与“接口错误”区分开。转写稿中后端用 Express 搭建了 3000 端口的 Web 服务,并监听 GET /api/province,读取 province.json 后通过 res.send 返回省份列表数据。这个接口本身是通的。
怎么想:调试时先验证“接口是否正常”。按转写稿的步骤,用 nodemon 启动后端入口文件(转写稿中“note more”应纠正为 nodemon),在浏览器直接访问 http://localhost:3000/api/province,能看到省份 JSON 数据,说明后端服务和接口没问题。然后再通过 LiveServer 打开页面,用 axios 发请求,此时控制台出现跨域报错。
怎么做:观察浏览器控制台报错,核心信息是 Access to XMLHttpRequest,意思为 XHR 链接出了问题:从一个源 http://localhost:5500 跨域访问 http://localhost:3000。这说明请求被浏览器按跨域规则拦截了,而不是省份接口没有返回数据。

易错点:看到 catch 里打印错误,不要立刻去改 axios 的 url 或怀疑后端路由。先确认“直接访问接口是否成功”;如果直接访问成功,而页面 AJAX 失败,并伴随 Access to XMLHttpRequest,就要优先检查页面源和接口源的协议、域名、端口是否完全一致。还要注意控制台报错里的两个源,正是定位问题的关键。
小结:跨域访问的判断与调试顺序
为什么需要:跨域问题在前后端联调中很常见,形成固定判断流程,才能快速定位而不是盲目改代码。
怎么想:什么是跨域访问?网页所在源和 AJAX 请求的源(协议、域名、端口号)不完全相同,就发生跨域访问。调试时先看接口能否直接访问,再看页面 AJAX 的请求地址,最后比较两个源的协议、域名、端口。
怎么做:保留并理解本节代码中的请求地址 http://localhost:3000/api/province;同时确认页面由 LiveServer 启动在 5500 端口。两边端口不同,所以出现跨域报错是符合预期的。转写稿中“艾克玛 script”应纠正为 ECMAScript,“common GS”应纠正为 CommonJS,“note more”应纠正为 nodemon,这些同音或误读不影响本节核心:跨域看的是源,不是单个 URL 是否能打开。
易错点:不要把“跨域”理解成“跨文件夹”“跨页面”或“跨接口路径”;也不要只改请求路径而忽略端口。只要协议、域名、端口有一项不同,就仍然可能跨域。
课件图示








代码示例
示例 1:用 Express 快速搭建 Web 服务(server.js)
js
/**
* 目标:基于 express 本地软件包,开发 Web 服务,返回资源给请求方
*/
// 1. 下载 express 软件包
// 在项目文件夹下先执行:npm init -y 得到 package.json
// 再执行: npm i express 得到 node_modules
// 2. 导入并创建 Web 服务对象
const express = require('express')
const server = express()
// 3. 监听请求的方法和请求的资源路径
server.get('/', (req, res) => {
res.send('你好,欢迎使用 Express')
})
// 4. 监听任意请求的方法和请求的资源路径
server.all('*', (req, res) => {
res.status(404)
res.send('你要访问的资源路径不存在')
})
// 5. 监听端口号,启动 Web 服务
server.listen(3000, () => {
console.log('Web 服务已启动')
})关键行与易错点
npm init -y生成项目清单package.json,-y表示所有配置项用默认值;使用-y时所在文件夹名不能有中文和特殊符号,建议只用英文小写和数字。npm i express下载本地软件包,源码落在当前项目的node_modules里;express 依赖的其他包会被一并下载,所以安装数量会显示几十个。const server = express():express 导出的是一个函数,必须调用它才能拿到 Web 服务对象,直接server.get会报错。server.get(...)只监听 GET 且路径完全匹配;express 会自动截掉查询参数再比对路径,不用自己写if/else判断。res.send()代替原生res.end(),它会自动设置响应头,所以返回中文不需要手动设Content-Type。server.all('*')是兜底路由,必须写在所有具体路由之后,否则会把前面的接口全部吃掉;*是通配符,匹配任意路径。- 404 要先
res.status(404)再res.send(...),只写 send 状态码仍是 200。 - 别忘了
server.listen(3000):浏览器地址栏输入http://localhost:3000/时,默认发起的是 GET 请求,POST/PUT/DELETE 在地址栏里发不出来,得用 AJAX。 - 启动命令用
node server.js,也可以用全局软件包nodemon server.js自动监测代码变化并重启。
示例 2:获取省份列表接口(server.js)
js
/**
* 目标:基于 express 的 Web 服务,编写提供省份列表数据的接口
*/
const express = require('express')
const fs = require('fs')
const path = require('path')
const server = express()
// 监听 get 请求方法,监听资源路径 /api/province,就读取 province.json 省份数据返回
server.get('/api/province', (req, res) => {
fs.readFile(path.join(__dirname, 'data/province.json'), (err, data) => {
res.send(data.toString())
})
})
// 兜底:其他任意请求方法 + 任意路径统一返回 404
server.all('*', (req, res) => {
res.status(404)
res.send('你要访问的资源路径不存在')
})
server.listen(3000, () => {
console.log('Web 服务已启动')
})关键行与易错点
- 想监听什么请求方法,就调用 Web 服务对象上同名的方法:GET 用
server.get,POST 用server.post,参数一是要监听的资源路径,参数二是命中后执行的回调。 - 路径必须写成
/api/province,浏览器里要一字不差地请求http://localhost:3000/api/province,否则会落到兜底的 404 分支。 fs.readFile的路径必须用绝对路径,所以用path.join(__dirname, 'data/province.json')拼接;__dirname是当前文件所在文件夹的绝对地址。直接写相对路径'data/province.json'会相对“启动命令所在目录”解析,换个目录启动就找不到文件。- 回调里读到的
data是 Buffer 二进制流,必须data.toString()转成字符串再res.send(),否则返回的内容不对。 - 读取失败时
err有值、data为 undefined,直接data.toString()会抛异常,实际项目中应先判断err。 - 想继续加接口,就在这个文件里再写一套
server.get('/api/xxx', ...)即可,兜底路由始终放在最后。
示例 3:体验跨域问题(index.html)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>跨域问题体验</title>
</head>
<body>
<!-- 引入 axios,供下方脚本发起 AJAX 请求 -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 目标:请求本机 Web 服务提供的省份列表数据
axios({
url: 'http://localhost:3000/api/province',
// method: 'get'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
</script>
</body>
</html>关键行与易错点
- 这个页面要用 LiveServer 打开,地址是
http://localhost:5500/...;而 AJAX 请求的是http://localhost:3000/api/province。端口号不同,所以发生跨域。 - 源由协议、域名、端口号三部分组成;网页加载时所在的源与 AJAX 请求的源三者全部相同才叫同源,有一个不同就是跨域。
- 跨域时浏览器控制台会报
Access to XMLHttpRequest ...,说明请求被浏览器的同源策略拦截;此时后端其实已经正常响应了,是浏览器不让页面拿到结果。 - 同源策略的作用是保护网站安全,限制一个源对另一个源的资源交互,限制的是 AJAX 只能访问同源 URL(钓鱼网站伪造请求转账就是典型攻击场景)。
method: 'get'可以省略,axios 默认就是 GET。- 注意把后端服务先跑起来(
nodemon server.js),后端没启动时看到的是连接失败错误,不是跨域错误,两者要区分开。
常见错误
Cannot find module 'express'- 现象:执行
node server.js时,终端直接报错并退出,服务无法启动。 - 原因:没有安装 express;或者在 A 文件夹安装,却在 B 文件夹运行;或者
node_modules与package.json不在同一级目录。 - 修法:先
cd到含package.json的目录,再执行npm i express,确认node_modules/express存在后再运行。
- 现象:执行
npm init -y报错或包名非法- 现象:终端提示 name 不合法,或生成后下载软件包时报错。
- 原因:所在文件夹名含中文、空格、大写或特殊符号。
- 修法:把文件夹改成英文小写加数字命名,删掉旧的
package.json后重新初始化。
Cannot use import statement outside a module/require is not defined- 现象:一加导入语句,程序立刻报错。
- 原因:CommonJS 与 ECMAScript 两套标准在同一项目里混用了。
- 修法:一个项目统一一套标准;坚持 ESM 时在
package.json加"type": "module",否则一律require+module.exports。
导出后拿到的是空对象
- 现象:
require('./a')得到{},或拿到的不是想要的内容。 - 原因:在文件里写了
exports = {...},只是给局部变量重新赋值,与module.exports断了引用。 - 修法:写成
module.exports = {...},或逐个挂exports.fn = fn。
- 现象:
接口访问返回自己写的“资源路径不存在”
- 现象:明明写了
/api/province,浏览器却返回兜底提示,状态码 404。 - 原因:
server.all('*')写在了具体路由前面,先被通配匹配走了;或路径拼写、大小写不一致;或代码监听post,而浏览器地址栏发的是get。 - 修法:兜底路由永远放到所有具体路由之后;核对路径逐字符;用与监听一致的方法发起请求。
- 现象:明明写了
EADDRINUSE: address already in use :::3000- 现象:
nodemon启动失败,提示端口被占用。 - 原因:上次的 node 进程没退出,或另一个服务已占用 3000。
- 修法:终端
Ctrl+C结束残留进程,或换端口(如 3001),不要改代码逻辑来回避。
- 现象:
读文件报
ENOENT或返回乱码- 现象:接口 500,或返回内容不是 JSON。
- 原因:用了相对路径,相对的是执行命令时的工作目录,不是文件所在目录;且
Buffer未转字符串。 - 修法:用
path.join(__dirname, 'data/province.json')拼绝对路径,res.send(data.toString())。
页面请求接口报
blocked by CORS policy- 现象:Live Server 打开的页面请求
localhost:3000被拦,控制台红字Access to XMLHttpRequest ...。 - 原因:页面源(5500)与接口源(3000)端口不同,非同源,被浏览器同源策略拦截。
- 修法:服务端加 CORS 响应头(如
cors中间件),或让页面与接口同源(用 express 托管静态页),前端改代码无效。
- 现象:Live Server 打开的页面请求
练习
从零起服务:在空文件夹里完成“初始化清单 → 装 express → 写
server.js监听GET /返回{ ok: true }→ 用 nodemon 启动”全过程,把敲过的命令按顺序记录下来。路由顺序实验:把
server.all('*', ...)挪到server.get('/api/province', ...)之前,访问接口观察现象,写出原因,再改回正确顺序验证。扩展接口:让
/api/province支持查询参数,如/api/province?name=河,只返回名称包含“河”的省份(提示:req.query),无参数时返回全部。复现跨域:用 Live Server 打开页面,axios 请求 3000 的省份接口,抄下控制台完整报错,逐项指出两个源的协议/域名/端口哪里不同;再用
express.static托管该页面使其与接口同源,验证不再报错。两套标准对照:写
a.js导出两个工具函数,b.js按需导入使用,先 CommonJS 版,再改写为 ESM 命名导出/导入版,运行两个版本并列出语法差异点。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | Node.js概念和常用命令总结 | 08:54 |
| 2 | Express快速搭建Web服务 | 16:20 |
| 3 | 案例-获取省份列表接口 | 08:07 |
小结
- 新建项目的固定顺序是“建英文小写目录 →
npm init -y→ 下包 → 写代码”,顺序一乱,报错就来。 node_modules必须与package.json同级同目录,require才能找到包,跨目录执行命令是最隐蔽的坑。- 一个项目只允许一套模块标准,混用是最高频的报错来源,写第一行代码前就要定下来。
- 路由匹配有先后,兜底
all('*')必须写在最后,否则它会匹配掉所有请求。 - 读本机文件一律用
__dirname拼绝对路径,永远不要依赖相对路径。 - 浏览器地址栏只能发 GET,验证其它请求方法必须用 axios/fetch 或接口工具。
- 跨域是浏览器拦下来的,服务端其实已经返回了数据;解决方向只在服务端加 CORS 或同源部署。
- 服务起不来先看三件事:端口是否被占、
listen是否写了、回调里是否res.send了。