Express框架使用

前言

在 Node.js 原生开发中,我们可以使用内置 http 模块搭建 Web 服务,但原生 API 存在明显短板:需要手动判断请求路径、请求方式、解析请求参数、处理跨域与异常,代码臃肿、耦合度高、维护困难。

Express 是 Node.js 生态中最经典、使用最广泛的轻量级 Web 开发框架,它基于原生 http 模块二次封装,以中间件为核心,简化了路由管理、参数解析、文件上传、静态资源托管、异常处理等全流程操作,生态完善、上手简单,可用于开发接口服务、网站后端、静态服务器、小程序后端等各类场景。


一、Express 简介 & 环境搭建

1.1 为什么选择 Express

  1. 简化原生操作:无需手动处理 HTTP 请求头、流数据、路由匹配,框架内部封装完成;
  2. 中间件架构:功能可插拔,灵活扩展能力,第三方中间件生态极其丰富;
  3. 路由解耦:支持模块化路由,大型项目结构清晰;
  4. 轻量高效:本身只保留核心能力,按需引入功能,性能开销小;
  5. 社区成熟:文档完善、问题解决方案多,企业项目使用率极高。

Node 主流 Web 框架对比:Express(老牌稳定)、Koa(基于 Promise,语法更优雅)、NestJS(企业级全框架,TS 优先)。本文主打 Express 核心用法。

1.2 两种项目搭建方式

方式一:使用官方脚手架 express-generator(企业快速建项目)

脚手架会自动生成标准化目录结构、基础配置、路由文件,适合快速启动正式项目。

1)全局安装脚手架
复制代码
npm install -g express-generator
2)创建 Express 项目
bash 复制代码
# 创建名为 express-project 的项目
express express-project
3)启动项目
bash 复制代码
# 进入项目目录
cd express-project
# 安装依赖
npm install
# 启动服务
node bin/www
目录简单说明
  • bin/www:服务启动入口文件;
  • routes/:路由目录,自动拆分路由模块;
  • public/:默认静态资源目录;
  • views/:模板引擎目录(服务端渲染使用)。

方式二:从零手动搭建(推荐学习、自定义项目)

手动搭建能理解每一行代码作用,学习阶段首选。

1)初始化项目 & 安装 Express
csharp 复制代码
# 初始化 package.json
npm init -y
# 安装 express 生产依赖
npm install express

二、Express 基础入门(核心语法)

2.1 创建基础 Web 服务

完整可运行代码,包含创建实例、注册接口、监听端口三大核心步骤。

js 复制代码
// 1. 引入 express 模块
const express = require('express');
// 2. 创建 Express 服务实例 app(整个项目的核心对象)
const app = express();
// 3. 定义监听端口
const PORT = 8000;

// 4. 注册接口:app.请求方式(路由地址, 回调函数)
// GET 请求接口
app.get('/', (req, res) => {
  res.end('欢迎访问 Express 首页');
});

// POST 请求接口
app.post('/login', (req, res) => {
  res.end('登录接口请求成功');
});

// 5. 监听端口,启动服务器
app.listen(PORT, () => {
  console.log(`服务器启动成功,访问地址:http://localhost:${PORT}`);
});

核心对象说明

  • app:Express 实例,所有路由、中间件、配置都基于该对象;
  • req(Request):请求对象,客户端传给服务端的所有数据(参数、请求头、IP、路径等);
  • res(Response):响应对象,服务端返回给客户端的数据(文本、JSON、状态码、文件等)。

2.2 常用 HTTP 请求方法

Express 支持所有标准 HTTP 请求方式,开发中最常用 5 种:

js 复制代码
// GET:查询数据
app.get('/user', (req, res) => res.send('查询用户'));
// POST:新增数据、提交表单
app.post('/user', (req, res) => res.send('新增用户'));
// PUT:全量更新数据
app.put('/user/:id', (req, res) => res.send('修改用户'));
// PATCH:局部更新数据
app.patch('/user/:id', (req, res) => res.send('局部修改用户'));
// DELETE:删除数据
app.delete('/user/:id', (req, res) => res.send('删除用户'));

2.3 路由分类 & 动态路由

路由的本质:根据请求地址 + 请求方式,匹配对应的处理函数

2.3.1 静态路由

地址完全固定,精准匹配:

js 复制代码
// 仅匹配:http://localhost:8000/about
app.get('/about', (req, res) => {
  res.send('关于我们页面');
});

2.3.2 动态路由(路径参数)

路由中使用 :参数名 定义动态参数,用于匹配可变路径,例如用户 ID、文章 ID、商品 ID。

  • 取值方式:req.params.参数名
  • 支持多个动态参数
js 复制代码
// 单个动态参数:匹配 /user/1001、/user/2002
app.get('/user/:userId', (req, res) => {
  // 获取路径参数
  const userId = req.params.userId;
  res.json({
    code: 200,
    msg: '获取用户ID成功',
    userId
  });
});

// 多个动态参数:匹配 /order/100/200
app.get('/order/:orderId/:goodsId', (req, res) => {
  const { orderId, goodsId } = req.params;
  res.json({ orderId, goodsId });
});

2.3.3 通配符路由(模糊匹配)

用于统一处理 404 页面、模糊路径匹配,* 代表通配符。

js 复制代码
// 匹配所有未定义的接口,统一返回 404
app.get('*', (req, res) => {
  res.status(404).send('404 页面不存在');
});

2.4 响应客户端的常用 API

Express 在原生 res.end 基础上封装了大量便捷响应方法,开发高频使用。

1)res.send() 通用响应

支持字符串、HTML 片段、Buffer,自动设置响应头,是最常用的基础方法。

js 复制代码
app.get('/send-demo', (req, res) => {
  // 响应普通文本
  res.send('普通文本内容');
  // 响应 HTML 片段
  // res.send('<h1>这是HTML标题</h1>');
});

2)res.json() 响应 JSON(接口开发首选)

自动将对象/数组转为 JSON 字符串,自动设置 Content-Type: application/json,前后端接口必备。

js 复制代码
app.get('/json-demo', (req, res) => {
  const data = {
    code: 200,
    message: '请求成功',
    data: [1, 2, 3]
  };
  res.json(data);
});

3)res.status() 设置 HTTP 状态码

HTTP 状态码:2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务端错误)。

res.status(状态码) 支持链式调用

js 复制代码
app.get('/status-demo', (req, res) => {
  // 400 参数错误、401 未登录、403 无权限、404 地址不存在、500 服务异常
  res.status(400).json({ code: 400, msg: '参数错误' });
});

4)res.redirect() 重定向

跳转到指定地址(站内/站外地址均可)。

js 复制代码
// 访问 /go 自动跳转到 /home
app.get('/go', (req, res) => {
  res.redirect('/home');
});

5)res.sendFile() 响应本地文件

直接返回服务器本地文件(HTML、图片、文档等)。

js 复制代码
const path = require('path');
app.get('/file', (req, res) => {
  // __dirname:当前文件所在绝对路径
  const filePath = path.join(__dirname, 'index.html');
  res.sendFile(filePath);
});

三、Express 核心:中间件(重中之重)

中间件是 Express 的灵魂 ,整个 Express 应用的执行流程就是一串中间件依次执行。所有路由、参数解析、日志、跨域本质都是中间件。

3.1 中间件概念与语法

3.1.1 定义

中间件是一个回调函数,固定接收三个参数:

  1. req:请求对象
  2. res:响应对象
  3. next:放行函数(核心)

3.1.2 中间件能做什么

  1. 执行任意业务逻辑(打印日志、身份校验);
  2. 修改 req / res 对象(挂载参数、扩展方法);
  3. 直接结束请求响应(返回数据);
  4. 通过 next() 放行,执行下一个中间件/路由。

3.1.3 核心规则

如果当前中间件没有调用 res 相关方法结束响应必须执行 next(),否则请求会被挂起(浏览器一直转圈)。

3.2 中间件分类(按使用场景划分)

3.2.1 全局中间件(app.use())

使用 app.use(回调函数) 注册,所有客户端请求都会先走全局中间件,挂载顺序从上到下依次执行。

示例1:全局日志中间件

统一打印所有请求的路径、请求方式:

js 复制代码
const express = require('express');
const app = express();

// 全局中间件:所有请求都会执行
app.use((req, res, next) => {
  console.log(`请求方式:${req.method},请求地址:${req.url}`);
  // 放行,执行后续代码
  next();
});

// 路由接口
app.get('/home', (req, res) => {
  res.send('首页');
});

app.listen(8000);
示例2:全局身份校验(模拟登录拦截)
js 复制代码
// 全局拦截:模拟校验 token
app.use((req, res, next) => {
  const token = req.headers.token;
  if (!token) {
    // 未登录,直接结束响应,不再执行后续路由
    return res.status(401).json({ msg: '未登录,请先授权' });
  }
  next();
});

// 以下所有接口都会被身份拦截
app.get('/user-info', (req, res) => {
  res.send('用户信息');
});

3.2.2 路由级中间件(指定路径生效)

app.use(路径, 回调)仅匹配指定路径的请求才会执行

js 复制代码
// 仅 /user 开头的请求执行该中间件
app.use('/user', (req, res, next) => {
  console.log('进入用户模块中间件');
  next();
});

// 会触发中间件
app.get('/user/list', (req, res) => res.send('用户列表'));
// 不会触发中间件
app.get('/order/list', (req, res) => res.send('订单列表'));

3.2.3 路由内中间件(单个接口专属)

写在接口路由内部,仅对当前接口生效,支持注册多个中间件。

js 复制代码
// 单个接口多个中间件
app.get(
  '/api/test',
  // 第一个中间件
  (req, res, next) => {
    console.log('接口前置校验1');
    next();
  },
  // 第二个中间件
  (req, res, next) => {
    console.log('接口前置校验2');
    next();
  },
  // 最终接口处理函数
  (req, res) => {
    res.send('接口响应成功');
  }
);

3.2.4 错误处理中间件(专属四参数)

专门捕获全局异常,固定 4 个参数 (err, req, res, next),必须放在所有路由、中间件最后

js 复制代码
// 模拟异常接口
app.get('/error', (req, res, next) => {
  // 抛出错误,交给错误中间件处理
  const err = new Error('服务器内部异常');
  next(err);
});

// 全局错误处理中间件(四参数)
app.use((err, req, res, next) => {
  console.error('错误信息:', err.message);
  // 统一格式返回错误
  res.status(500).json({
    code: 500,
    msg: err.message
  });
});

3.3 中间件执行顺序(重点考点)

  1. 代码从上到下依次执行
  2. 全局中间件 > 路由级中间件 > 路由内中间件 > 接口处理函数;
  3. 一旦调用 res.send / res.json 等结束响应的方法,后续中间件不再执行
  4. 错误中间件只有调用 next(错误对象) 时才会触发。

四、五大客户端传参方式(全场景覆盖)

客户端向服务端传递参数一共有 5 种主流方式,对应不同解析方案,是接口开发的核心内容。

前置说明

所有 POST 请求的请求体参数,都需要对应解析中间件 才能通过 req.body 获取。

4.1 方式一:URL 查询参数(query 参数)

场景

地址栏拼接 ?键=值&键=值,常用于搜索、分页、筛选。

  • 地址示例:http://localhost:8000/search?keyword=express&page=1&size=10
  • 取值:req.query(Express 原生自动解析,无需额外中间件)
js 复制代码
app.get('/search', (req, res) => {
  // 解构获取参数
  const { keyword, page, size } = req.query;
  res.json({
    关键词: keyword,
    页码: page,
    条数: size
  });
});

4.2 方式二:动态路径参数(params 参数)

前文动态路由已讲解,适用于 ID、唯一标识类参数。

  • 地址:/user/1001
  • 取值:req.params

4.3 方式三:POST JSON 格式参数(接口最常用)

前端使用 axios/fetch 发送 JSON 数据,请求头 Content-Type: application/json

解析中间件

express.json()(Express 内置),必须在路由前挂载。

js 复制代码
const express = require('express');
const app = express();

// 解析 JSON 格式请求体(必须放在路由前面)
app.use(express.json());

// 接收 JSON 格式 POST 请求
app.post('/login', (req, res) => {
  // req.body 获取 JSON 参数
  const { username, password } = req.body;
  res.json({
    code: 200,
    msg: '登录成功',
    username,
    password
  });
});

app.listen(8000);

4.4 方式四:POST 表单格式(x-www-form-urlencoded)

传统网页 form 表单默认格式,Content-Type: application/x-www-form-urlencoded

解析中间件

express.urlencoded({ extended: true })

js 复制代码
const express = require('express');
const app = express();

// 解析普通表单参数
app.use(express.urlencoded({ extended: true }));

app.post('/form', (req, res) => {
  const { username, password } = req.body;
  res.json({ msg: '表单接收成功', data: req.body });
});

参数说明:extended: true 允许解析复杂对象、数组,固定配置即可。

4.5 方式五:POST form-data 格式(文件上传+混合表单)

专门用于文件上传 ,也可传递普通文本参数,请求头 Content-Type: multipart/form-data

Express 无内置解析能力,使用第三方中间件 multer

4.5.1 安装 multer

复制代码
npm install multer

4.5.2 基础单文件上传

js 复制代码
const express = require('express');
const multer = require('multer');
const app = express();

// 配置:文件保存到 uploads 文件夹
const upload = multer({ dest: 'uploads/' });

// single('file') 对应前端文件字段名
app.post('/upload', upload.single('file'), (req, res) => {
  // req.file:文件信息
  console.log(req.file);
  res.json({ msg: '文件上传成功' });
});

app.listen(8000);

4.5.3 自定义文件名 & 存储路径(解决重名覆盖)

结合 multer.diskStorage 自定义存储规则,使用时间戳防止文件重名:

js 复制代码
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();

// 自定义存储规则
const storage = multer.diskStorage({
  // 定义存储目录
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  // 定义文件名:时间戳 + 原文件后缀
  filename: (req, file, cb) => {
    const suffix = path.extname(file.originalname);
    const fileName = Date.now() + suffix;
    cb(null, fileName);
  }
});

// 应用配置
const upload = multer({ storage });
app.post('/upload-file', upload.single('file'), (req, res) => {
  res.json({ msg: '自定义文件名上传成功' });
});

4.5.4 解析 form-data 普通文本参数

使用 upload.any() 解析表单中的普通字段:

js 复制代码
const upload = multer();
app.post('/formdata-login', upload.any(), (req, res) => {
  // 普通参数依旧在 req.body
  res.json({ data: req.body });
});

upload.any()和其他multer的方法的差别如下:

方法 作用 适用场景
upload.single('file') 只接收 1 个文件,字段名必须是 file 单文件上传,固定字段
upload.array('img', 5) 接收最多 5 个文件,统一字段名 img 多图批量上传
upload.fields([{name:'a'},{name:'b'}]) 接收指定几个固定字段的文件 页面多个上传框,字段已知
upload.any() 不限制字段名、不限制文件数量,所有文件全部接收 不确定前端上传字段、动态字段

💡提示:any方法由于对什么都不进行限制,所以生产环境慎用!

4.6 补充:手动解析 Body(理解底层原理)

不使用内置中间件,基于 Node 流手动解析 JSON 数据,面试常考:

js 复制代码
app.use((req, res, next) => {
  // 判断请求类型为 JSON
  if (req.headers['content-type'] === 'application/json') {
    let data = '';
    // 分段接收流数据
    req.on('data', chunk => {
      data += chunk.toString();
    });
    // 数据接收完成
    req.on('end', () => {
      req.body = JSON.parse(data);
      next();
    });
  } else {
    next();
  }
});

五、模块化路由 Router(大型项目必备)

当接口数量越来越多,所有代码写在 app.js 中会臃肿难维护。Express 提供 express.Router() 实现路由拆分、模块化管理,每个业务模块独立一个路由文件。

5.1 路由使用流程

  1. 创建独立路由文件;
  2. express.Router() 创建路由实例(迷你应用);
  3. 在路由实例上编写接口;
  4. 主文件引入路由,通过 app.use(路由前缀, 路由实例) 挂载。

5.2 完整实战案例

项目目录结构

bash 复制代码
├── app.js          # 主入口文件
└── routes/         # 路由文件夹
    ├── user.js     # 用户模块路由
    └── order.js    # 订单模块路由

1)用户路由 routes/user.js

js 复制代码
const express = require('express');
// 创建路由实例
const userRouter = express.Router();

// 接口:最终地址 /users/
userRouter.get('/', (req, res) => {
  res.json({ msg: '获取用户列表' });
});

// 接口:最终地址 /users/:id
userRouter.get('/:id', (req, res) => {
  res.json({ userId: req.params.id, msg: '获取单个用户' });
});

// 新增用户
userRouter.post('/', (req, res) => {
  res.json({ msg: '新增用户', data: req.body });
});

// 导出路由
module.exports = userRouter;

2)主入口 app.js

js 复制代码
const express = require('express');
const app = express();

// 解析请求体
app.use(express.json());

// 引入路由模块
const userRouter = require('./routes/user');
const orderRouter = require('./routes/order');

// 挂载路由:统一添加前缀 /users
app.use('/users', userRouter);
app.use('/orders', orderRouter);

app.listen(8000, () => {
  console.log('服务启动');
});

5.3 路由嵌套(二级路由)

路由内部也可以继续嵌套子路由,适用于复杂业务。


六、静态资源服务器

Express 内置 express.static() 中间件,一行代码快速搭建静态服务器,托管 HTML、图片、JS、CSS、视频等静态文件。

6.1 基础使用

js 复制代码
const express = require('express');
const app = express();

// 托管当前目录下的 build 文件夹为静态资源目录
app.use(express.static('./build'));

app.listen(8000);

使用方式:将静态文件放入 build 文件夹,直接通过 http://localhost:8000/文件名 访问。

6.2 配置虚拟前缀

给静态资源添加访问前缀,区分接口和静态资源:

bash 复制代码
// 访问地址必须加 /static 前缀:http://localhost:8000/static/图片.jpg
app.use('/static', express.static('./build'));

6.3 托管多个静态目录

支持同时托管多个文件夹:

php 复制代码
app.use(express.static('./build'));
app.use(express.static('./public'));

七、日志中间件 morgan 完整版(生产环境必备)

morgan 是 Express 官方推荐的日志中间件,用于记录所有 HTTP 请求信息,方便线上排查问题。

7.1 安装

复制代码
npm install morgan

7.2 核心语法

scss 复制代码
morgan(日志格式, 配置项)

7.3 五种内置日志格式

1)dev(开发环境)

彩色简洁日志,控制台友好,仅本地开发使用

js 复制代码
const express = require('express');
const morgan = require('morgan');
const app = express();

app.use(morgan('dev'));
app.get('/', (req, res) => res.send('首页'));
app.listen(8000);

输出示例:GET / 200 1.2 ms - 4

2)combined(生产环境标准格式)

信息最全,包含 IP、时间、请求头、UA,线上环境首选:

less 复制代码
app.use(morgan('combined'));

3)common / short / tiny

极简格式,根据需求选择。

7.4 日志写入本地文件(持久化)

默认输出到控制台,线上需存入文件,使用文件流追加写入:

js 复制代码
const express = require('express');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
const app = express();

// 创建追加写入流
const writeStream = fs.createWriteStream(path.join(__dirname, 'access.log'), {
  flags: 'a', // 追加模式,不覆盖旧日志
  encoding: 'utf-8'
});

// 日志写入文件
app.use(morgan('combined', { stream: writeStream }));

app.listen(8000);

7.5 日志按日期切割(线上优化)

单日志文件会越来越大,使用 file-stream-rotator 按天切割日志:

安装依赖

复制代码
npm install file-stream-rotator

按天分割完整代码

js 复制代码
const express = require('express');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
const FileStreamRotator = require('file-stream-rotator');
const app = express();

const logDir = path.join(__dirname, 'logs');
// 自动创建日志目录
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir);

// 配置切割规则
const rotateStream = FileStreamRotator.getStream({
  date_format: 'YYYYMMDD',
  filename: path.join(logDir, 'access-%DATE%.log'),
  frequency: 'daily', // 按天切割
  max_logs: 15 // 保留15天日志
});

app.use(morgan('combined', { stream: rotateStream }));
app.listen(8000);

7.6 日志过滤(skip)

忽略静态资源、只记录错误日志:

javascript 复制代码
// 只记录 4xx/5xx 错误请求
app.use(morgan('dev', {
  skip: (req, res) => res.statusCode < 400
}));

// 忽略静态资源请求
app.use(morgan('combined', {
  skip: (req) => /.(css|js|png|jpg|ico)$/.test(req.url)
}));

7.7 自定义日志格式与字段

ini 复制代码
// 自定义日志模板
const customLog = ':date[iso] :method :url :status :remote-addr';
app.use(morgan(customLog));

// 自定义 Token 字段
morgan.token('token', req => req.headers.token || '无token');

八、扩展常用功能

8.1 跨域处理(CORS)

前后端分离项目必用,使用 cors 中间件快速解决跨域。

安装

复制代码
npm install cors

全局开启跨域

ini 复制代码
const cors = require('cors');
const app = express();
// 全局允许所有跨域请求
app.use(cors());

8.2 多环境配置(开发/生产区分)

通过 NODE_ENV 区分环境,不同环境使用不同日志、配置:

php 复制代码
const env = process.env.NODE_ENV || 'development';
if (env === 'development') {
  app.use(morgan('dev'));
} else {
  // 生产环境:日志写入文件
  app.use(morgan('combined', { stream: rotateStream }));
}

启动命令:

ini 复制代码
# 开发环境
node app.js
# 生产环境(Mac/Linux)
NODE_ENV=production node app.js

九、全文速查总结

  1. 服务创建express() 创建实例 → app.METHOD() 注册接口 → app.listen() 启动;
  2. 中间件 :核心机制,app.use() 注册,无响应必须执行 next()
  3. 参数获取
    • 路径参数:req.params
    • 查询参数:req.query
    • JSON/表单:req.body(依赖对应解析中间件)
    • 文件:multer + req.file
  1. 路由拆分express.Router() 模块化管理接口;
  2. 静态服务express.static(目录路径)
  3. 错误处理 :四参数中间件 (err, req, res, next)
  4. 日志 :开发用 dev,生产用 combined,线上必须做日志切割;
  5. 跨域 :引入 cors 中间件一键解决。
相关推荐
IT_陈寒4 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码5 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo5 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志6 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..6 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects6 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰6 小时前
一些基础和问题解决
后端
陈随易6 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN6 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana6 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端