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 中间件一键解决。
相关推荐
yinchnag1 小时前
Go 语言 map 底层实现
后端·源码阅读
MacroZheng1 小时前
横空出世!Claude Code画图神器来了,比Visio快10倍!
java·人工智能·后端
布局呆星1 小时前
Spring Boot + AOP 操作日志实战:自定义注解、切面编程、SecurityContext 全链路贯通,一次讲透
java·spring boot·后端
lazy H1 小时前
Maven 依赖爆红怎么办?IDEA 中 Maven 项目常见问题和解决方法总结
java·后端·学习·maven·intellij-idea
CodeSheep1 小时前
又是梁文锋,有点猛啊。
前端·后端·程序员
SimonKing1 小时前
低调低调,白嫖文生图,文生视频模型,无Token限制
java·后端·程序员
我登哥MVP1 小时前
SpringCloud Alibaba 核心组件解析:服务熔断和降级
java·spring boot·后端·spring·spring cloud·java-ee·maven
aramae1 小时前
《计算机网络(第5版)》第二章 物理层
服务器·网络·后端·计算机网络
lazy H1 小时前
Spring Boot 连接 MySQL 失败怎么办?常见报错原因和解决方法总结
spring boot·后端·学习·mysql·spring