2. 搭建第一个Express服务

上一节我们理解了 Express 在 Node.js HTTP 模块之上提供的抽象价值。这一节要把抽象落到手上:从空目录开始,初始化项目、安装依赖、创建 app 对象、注册第一条路由,并让服务在本地真正跑起来、能被浏览器和调试工具观察到。整个过程围绕一个最小但完整的服务闭环展开,每一步都有明确的输入与可验证的输出。

项目初始化:让目录成为可管理的 Node 工程

先创建一个空目录并进入,例如 first-express。此时它只是一个普通文件夹,Node 并不认识它。执行初始化命令后,会生成 package.json,这是工程的“身份证”,记录项目名称、版本、入口文件、脚本和依赖清单。

mkdir first-express
cd first-express
npm init -y

-y 表示接受默认值,快速生成。生成的 package.json 中,main 默认指向 index.js,scripts 里有一个占位的 test。我们稍后会修改 scripts,加入启动命令。需要说明的是,npm init -y 生成的字段是 npm 的默认约定,不是 Express 的要求;Express 只要求 Node 能通过入口文件加载到应用代码。

安装依赖:区分 dependencies 与 devDependencies

Express 是运行时依赖,服务启动后必须存在,因此用普通安装:

npm install express

安装完成后,package.json 的 dependencies 中会出现 express 及其版本号,同时目录下多出 node_modules 和 package-lock.json。node_modules 存放实际安装的包,package-lock.json 锁定依赖树的精确版本,保证不同机器安装结果一致。这两者中,node_modules 通常不提交到版本库,而 package.json 与 package-lock.json 应当提交。

开发阶段还会用到自动重启工具,例如 nodemon,它只在开发时使用,应安装为开发依赖:

npm install --save-dev nodemon

这样它进入 devDependencies,生产环境安装时可通过 npm install --production 跳过,避免把调试工具带上线。依赖装好后,在 package.json 的 scripts 中加入:

"scripts": {
  "start": "node app.js",
  "dev": "nodemon app.js"
}

这里把入口文件命名为 app.js,与默认的 index.js 不同,因此要显式写出。之后 npm run dev 即可在文件保存后自动重启服务。

app 对象:Express 应用的核心载体

新建 app.js,写入最小可运行代码:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello Express');
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

这段代码里,express() 返回的就是 app 对象。它不是一个普通的空对象,而是承载了路由表、中间件栈和配置的调度中心。调用 app.get() 时,Express 把“路径 + 方法 + 处理函数”登记进内部路由结构;调用 app.listen() 时,app 内部创建 HTTP 服务器并开始监听端口。请求到达后,app 按注册顺序匹配路由,命中后执行对应处理函数,并通过 res 对象把响应写回客户端。

理解 app 对象的关键在于:它既是注册器(登记路由和中间件),又是调度器(在请求到来时分发处理)。后续章节要学的中间件、路由拆分、错误处理,都是围绕这个对象展开的。

Express 最小服务:从请求到响应的调度链 浏览器 / curl GET / app 对象(express() 返回) 路由表 GET / → 处理函数 (未注册的路径 → 404) 中间件栈(按注册顺序) 本节暂为空,后续章节填充 app.listen(3000) → 创建 HTTP Server 并监听端口 HTTP 响应 200 + Hello Express 请求进入 → app 匹配路由 → 执行处理函数 → res 写回响应

路由初体验:路径、方法与处理函数

路由的本质是“当某个 HTTP 方法访问某个路径时,执行哪段代码”。上面的 app.get('/', handler) 表示:只处理 GET 方法、路径为 / 的请求。再添加一条:

app.get('/about', (req, res) => {
  res.send('About Page');
});

启动后访问 http://localhost:3000/about 会得到 About Page;访问未注册的 /contact,Express 返回默认的 404 页面。这说明路由匹配是精确的,未命中不会自动兜底。

处理函数接收两个核心参数:req 封装请求信息,res 负责构造响应。res.send() 会根据内容自动设置 Content-Type,发送字符串时默认 text/html。若想返回 JSON,可用 res.json({ ok: true }),它会自动序列化并设置 application/json。这两个方法是最常用的响应出口,后续会在此基础上扩展状态码、响应头和重定向。

本地调试:观察启动、请求与错误

在终端执行 npm run dev,看到 Server running at http://localhost:3000 说明监听成功。此时可用浏览器访问,也可用 curl 验证:

curl -i http://localhost:3000/
# HTTP/1.1 200 OK
# Content-Type: text/html; charset=utf-8
# Hello Express

-i 会显示响应头,便于确认状态码和内容类型。若端口被占用,会抛出 EADDRINUSE,此时换端口或结束占用进程即可。若修改代码后没有生效,先确认是否用了 nodemon;用 node app.js 启动时不会自动重启,需要手动停止再运行。

调试建议:在路由处理函数中临时加入 console.log(req.method, req.url),可以在终端看到每次请求的方法与路径,快速判断请求是否命中预期路由。这是排查 404 和路由顺序问题最直接的手段。

一个可直接复用的最小工程清单

项目内容作用
入口文件app.js创建 app、注册路由、启动监听
运行时依赖express提供 app 对象与路由能力
开发依赖nodemon文件变更后自动重启
启动脚本npm run dev开发时启动并监听改动
验证方式curl -i 或浏览器确认状态码、响应头与响应体