node.js 中的 express 框架 - 基础到进阶

一、基础篇:Express 核心概念入门

1. 第一个 Express 应用:极简启动

先看一个最基础的 Express 应用,理解其核心结构:

JavaScript 复制代码
// 引入 Express 模块
const express = require('express');
// 创建 Express 应用实例
const app = express();
// 定义端口
const PORT = 3000;

// 定义根路由:处理 GET 请求
app.get('/', (req, res) => {
  res.send('Hello Express!');
});

// 启动服务器监听端口
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

核心解析

  • express():创建一个 Express 应用实例,封装了 HTTP 服务器的创建和请求处理逻辑。

  • app.get(path, handler):定义路由,监听 path 路径的 GET 请求,handler 是请求处理函数,接收 req(请求对象)和 res(响应对象)。

  • res.send():发送响应内容,自动设置 Content-Type(字符串默认 text/html,对象默认 application/json)。

2. 路由(Routing):请求的"导航系统"

路由是 Express 处理请求的核心,负责将不同的 HTTP 请求(方法+路径)映射到对应的处理函数。

(1)基本路由:支持所有 HTTP 方法
JavaScript 复制代码
// GET 请求:获取数据
app.get('/users', (req, res) => {
  res.json({ users: ['Alice', 'Bob'] });
});

// POST 请求:创建数据
app.post('/users', (req, res) => {
  res.status(201).json({ message: 'User created' });
});

// PUT 请求:更新数据(完整替换)
app.put('/users/:id', (req, res) => {
  res.json({ message: `User ${req.params.id} updated` });
});

// DELETE 请求:删除数据
app.delete('/users/:id', (req, res) => {
  res.json({ message: `User ${req.params.id} deleted` });
});
(2)路由参数与查询字符串
  • 路由参数 :通过 :name 定义,存储在 req.params 中(如 /users/:id 匹配 /users/123req.params.id = '123')。

  • 查询字符串 :URL 中 ? 后的参数,存储在 req.query 中(如 /users?page=1&limit=10req.query = { page: '1', limit: '10' })。

JavaScript 复制代码
app.get('/users/:id/posts', (req, res) => {
  const { id } = req.params;
  const { page = 1, limit = 10 } = req.query;
  res.json({ userId: id, page, limit });
});
(3)路由模块化:用 express.Router() 拆分代码

当路由增多时,将所有路由写在一个文件中会导致代码臃肿。使用 Router 可以将路由按模块拆分:

JavaScript 复制代码
// routes/users.js:用户模块路由
const express = require('express');
const router = express.Router();

// 注意:路径是相对于挂载点的(这里的 '/' 对应 '/users')
router.get('/', (req, res) => res.json({ users: [] }));
router.get('/:id', (req, res) => res.json({ user: req.params.id }));

module.exports = router;
JavaScript 复制代码
// app.js:主文件中挂载路由
const userRouter = require('./routes/users');
// 将 userRouter 挂载到 '/users' 路径下
app.use('/users', userRouter);
(4)路由链:简化同一资源的多个路由

对于同一资源(如 /users)的多个 HTTP 方法,可以用 app.route() 链式调用:

JavaScript 复制代码
app.route('/users')
  .get((req, res) => res.json({ users: [] }))
  .post((req, res) => res.status(201).json({ message: 'Created' }));

3. 中间件(Middleware):Express 的"灵魂"

中间件是 Express 最核心的设计理念------它是一个函数,在请求-响应周期中被调用,可以访问 reqresnext(下一个中间件函数)。中间件可以执行任何代码、修改请求/响应对象、结束请求-响应周期,或调用下一个中间件。

(1)中间件的分类与用法
  • 应用级中间件 :绑定到 app 实例,对所有请求生效(或指定路径生效)。

  • 路由级中间件 :绑定到 express.Router() 实例,仅对该路由模块生效。

  • 错误处理中间件 :专门处理错误,有 4 个参数 (err, req, res, next)

  • 内置中间件 :Express 自带的中间件(如 express.jsonexpress.static)。

  • 第三方中间件 :社区开发的中间件(如 morgancors)。

(2)内置中间件:最常用的两个
  • express.json():解析请求体中的 JSON 数据(存储到 req.body)。

  • express.urlencoded():解析 URL 编码的请求体(如表单提交,存储到 req.body)。

  • express.static():托管静态文件(如 HTML、CSS、图片)。

JavaScript 复制代码
// 解析 JSON 请求体(必须放在路由之前)
app.use(express.json());
// 解析 URL 编码请求体(extended: false 表示使用 querystring 库解析)
app.use(express.urlencoded({ extended: false }));
// 托管 public 目录下的静态文件(访问 /css/style.css 对应 public/css/style.css)
app.use(express.static('public'));
(3)自定义应用级中间件
JavaScript 复制代码
// 全局中间件:每个请求都会执行
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next(); // 调用下一个中间件,否则请求会挂起
});

// 路径特定中间件:仅对 /api 路径下的请求生效
app.use('/api', (req, res, next) => {
  console.log('Accessing API');
  next();
});
(4)错误处理中间件

错误处理中间件必须有 4 个参数,且要放在所有中间件和路由之后:

JavaScript 复制代码
// 模拟一个错误路由
app.get('/error', (req, res, next) => {
  const error = new Error('Something went wrong!');
  error.status = 500;
  next(error); // 将错误传递给错误处理中间件
});

// 错误处理中间件
app.use((err, req, res, next) => {
  const status = err.status || 500;
  const message = err.message || 'Internal Server Error';
  res.status(status).json({ error: message });
});

二、进阶篇:构建生产级应用

1. 统一错误处理:优雅处理同步与异步错误

Express 4.x 默认不会捕获异步代码中的错误(如 async/await 中的 throw),需要手动包装或使用工具。

(1)自定义错误类

先定义一个统一的错误类,方便传递状态码和错误信息:

JavaScript 复制代码
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true; // 标记为可预期的业务错误(非系统错误)
    Error.captureStackTrace(this, this.constructor);
  }
}
(2)异步错误包装器

用一个高阶函数包装异步路由,自动捕获错误并传递给错误处理中间件:

JavaScript 复制代码
const asyncWrapper = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next); // 捕获异步错误,传递给 next(err)
  };
};
(3)完整示例
JavaScript 复制代码
// 路由中使用
app.get('/users/:id', asyncWrapper(async (req, res, next) => {
  const user = await User.findById(req.params.id); // 模拟异步数据库查询
  if (!user) {
    throw new AppError('User not found', 404); // 抛出业务错误
  }
  res.json({ user });
}));

// 错误处理中间件(区分业务错误和系统错误)
app.use((err, req, res, next) => {
  const { statusCode = 500, message, isOperational } = err;
  res.status(statusCode).json({
    error: isOperational ? message : 'Internal Server Error',
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) // 开发环境返回堆栈
  });
});

2. RESTful API 设计实践

Express 非常适合构建 RESTful API,核心原则是"资源导向",用 HTTP 方法表示操作类型。

(1)资源命名与 HTTP 方法对应
资源 GET(获取) POST(创建) PUT(完整更新) PATCH(部分更新) DELETE(删除)
/users 获取所有用户 创建新用户 批量更新用户(不推荐) 批量部分更新(不推荐) 删除所有用户(不推荐)
/users/:id 获取指定 ID 的用户 不允许 替换指定 ID 的用户 部分更新指定 ID 的用户 删除指定 ID 的用户
(2)示例:用户管理 API
JavaScript 复制代码
// 模拟用户数据
let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
];

// 获取所有用户(支持分页)
app.get('/users', asyncWrapper(async (req, res) => {
  const { page = 1, limit = 10 } = req.query;
  const start = (page - 1) * limit;
  const end = start + parseInt(limit);
  const paginatedUsers = users.slice(start, end);
  res.json({
    data: paginatedUsers,
    page: parseInt(page),
    limit: parseInt(limit),
    total: users.length
  });
}));

// 创建用户
app.post('/users', asyncWrapper(async (req, res, next) => {
  const { name, email } = req.body;
  if (!name || !email) {
    throw new AppError('Name and email are required', 400);
  }
  const newUser = { id: users.length + 1, name, email };
  users.push(newUser);
  res.status(201).json({ data: newUser });
}));

3. 文件上传:Multer 中间件

Express 本身不支持文件上传,需要使用第三方中间件 multer

(1)安装与基本配置
Bash 复制代码
npm install multer
JavaScript 复制代码
const multer = require('multer');

// 配置存储:指定文件存储路径和文件名
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/'); // 存储到 uploads 目录(需提前创建)
  },
  filename: (req, file, cb) => {
    // 生成唯一文件名:时间戳 + 原始文件名
    const uniqueName = `${Date.now()}-${file.originalname}`;
    cb(null, uniqueName);
  }
});

// 配置文件过滤器:只允许图片上传
const fileFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('image/')) {
    cb(null, true); // 允许上传
  } else {
    cb(new AppError('Only images are allowed', 400), false); // 拒绝上传
  }
};

// 初始化 Multer
const upload = multer({
  storage,
  fileFilter,
  limits: { fileSize: 5 * 1024 * 1024 } // 限制文件大小 5MB
});
(2)单文件与多文件上传
JavaScript 复制代码
// 单文件上传:字段名为 avatar
app.post('/upload/avatar', upload.single('avatar'), (req, res) => {
  res.json({
    message: 'Avatar uploaded successfully',
    file: req.file // 上传的文件信息(路径、大小等)
  });
});

// 多文件上传:字段名为 photos,最多 5 个
app.post('/upload/photos', upload.array('photos', 5), (req, res) => {
  res.json({
    message: 'Photos uploaded successfully',
    files: req.files // 上传的文件数组
  });
});

4. 安全最佳实践

生产环境中必须关注安全,以下是 Express 应用的核心安全措施:

(1)Helmet:设置安全 HTTP 头

helmet 是一个安全中间件,通过设置各种 HTTP 头来防止常见的 Web 攻击(如 XSS、点击劫持)。

Bash 复制代码
npm install helmet
JavaScript 复制代码
const helmet = require('helmet');
app.use(helmet()); // 启用所有默认安全头
(2)CORS:配置跨域资源共享

如果 API 被前端跨域调用,需要配置 cors 中间件。

Bash 复制代码
npm install cors
JavaScript 复制代码
const cors = require('cors');
// 允许所有来源(开发环境用)
app.use(cors());
// 或仅允许特定来源(生产环境用)
app.use(cors({
  origin: 'https://your-frontend.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));
(3)速率限制:防止暴力攻击

express-rate-limit 限制同一 IP 的请求频率。

Bash 复制代码
npm install express-rate-limit
JavaScript 复制代码
const rateLimit = require('express-rate-limit');

// 全局速率限制:15 分钟内最多 100 个请求
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: 'Too many requests, please try again later'
});
app.use(globalLimiter);

// 登录接口单独限制:15 分钟内最多 5 个请求
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: 'Too many login attempts, please try again later'
});
app.post('/login', loginLimiter, (req, res) => {
  res.json({ message: 'Login successful' });
});
(4)输入验证:防止恶意数据

joiexpress-validator 验证请求参数和请求体。

Bash 复制代码
npm install joi
JavaScript 复制代码
const Joi = require('joi');

// 定义用户创建的验证 schema
const userSchema = Joi.object({
  name: Joi.string().min(2).max(30).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(18).max(100)
});

// 在路由中使用
app.post('/users', (req, res, next) => {
  const { error } = userSchema.validate(req.body);
  if (error) {
    throw new AppError(error.details[0].message, 400);
  }
  next(); // 验证通过,继续处理
}, (req, res) => {
  res.json({ message: 'User created' });
});

5. 性能优化

生产环境中需要关注性能,以下是几个核心优化点:

(1)压缩响应:使用 compression 中间件
Bash 复制代码
npm install compression
JavaScript 复制代码
const compression = require('compression');
app.use(compression()); // 压缩所有响应(放在路由之前)
(2)静态文件缓存

给静态文件设置 maxAge,让浏览器缓存:

JavaScript 复制代码
app.use(express.static('public', {
  maxAge: '1d' // 缓存 1 天
}));
(3)避免同步操作

所有 I/O 操作(数据库、文件系统)都使用异步方式,避免阻塞事件循环。

相关推荐
qq_229058014 小时前
使用nvm安装node12 以及对应的NPM6.14.16
node.js
zhensherlock4 小时前
Protocol Launcher 系列:Mail Assistant 轻松发送 HTML 邮件
前端·javascript·typescript·node.js·html·github·js
旺王雪饼 www6 小时前
《Express框架深度解析:从基础入门到高级实践与项目架构》
前端·node.js·express
星光不问赶路人1 天前
Node.js 如何判断入口文件:从 require.main 到 ES Module 实现
前端·node.js
网络点点滴1 天前
Node.js 中阻塞、非阻塞及异步特性
node.js
netkiller-BG7NYT1 天前
yoloutils - Openclaw Agent Skill
前端·webpack·node.js
cypking1 天前
npm 依赖包版本扫描提示插件Version Lens
前端·npm·node.js
研究点啥好呢2 天前
Github热门项目推荐 | 创建你的像素风格!
c++·python·node.js·github·开源软件