Skip to content

模块化设计

⏱️最后更新: 2025/07/27

1. 目录

bash
npm init -y
markdown
advance-002/
├── src/
│ ├── index.js # 入口文件(启动服务器、配置中间件)
│ ├── routes/ # 路由模块(定义接口路径)
│ │ └── userRoutes.js # 用户路由
│ ├── controllers/ # 控制器模块(处理请求/响应逻辑)
│ │ └── userController.js
│ ├── services/ # 服务模块(处理业务逻辑,与数据层交互)
│ │ └── userService.js
│ ├── utils/ # 工具模块(通用函数,如日期格式化、错误处理)
│ │ └── formatDate.js
│ └── middlewares/ # 中间件模块(全局/局部中间件)
│ └── errorHandler.js
├── .env # 环境变量配置
├── .gitignore # 忽略文件(如 node_modules、.env)
└── package.json # 项目配置

2. 安装依赖

bash
npm install express dotenv
npm install nodemon --save-dev

3. 编写模块化代码

3.1 环境变量配置(.env)

在项目根目录创建.env文件,定义端口等变量:

bash
PORT=3000
API_PREFIX=/api

3.2 入口文件(src/index.js)

加载环境变量、配置 Express、挂载路由和中间件:

javascript
// src/index.js
import express from "express";
import dotenv from "dotenv";
import userRoutes from "./routes/userRoutes.js"; // 导入用户路由(注意扩展名.js)
import errorHandler from "./middlewares/errorHandler.js"; // 导入错误处理中间件

// 加载环境变量
dotenv.config();

// 创建Express实例
const app = express();
const port = process.env.PORT || 3000;
const apiPrefix = process.env.API_PREFIX || "/api";

// 全局中间件
app.use(express.json()); // 解析JSON请求体
app.use(express.urlencoded({ extended: true })); // 解析URL编码的请求体

// 挂载路由(带API前缀)
app.use(`${apiPrefix}/users`, userRoutes);

// 错误处理中间件(需放在所有路由之后)
app.use(errorHandler);

// 启动服务器
app.listen(port, () => {
  console.log(`Server running on port ${port} (API prefix: ${apiPrefix})`);
});

3.3 路由模块(src/routes/userRoutes.js)

定义用户相关的路由,映射到控制器的方法:

javascript
// src/routes/userRoutes.js
import express from "express";
import userController from "../controllers/userController.js"; // 导入用户控制器

const router = express.Router();

// 定义路由:GET /api/users/:id(获取单个用户)
router.get("/:id", userController.getUserById);

// 定义路由:POST /api/users(创建用户)
router.post("/", userController.createUser);

export default router; // 导出路由实例

3.4 控制器模块(src/controllers/userController.js)

处理请求与响应逻辑,调用服务层的业务方法:

javascript
// src/controllers/userController.js
import userService from "../services/userService.js"; // 导入用户服务
import { formatDate } from "../utils/formatDate.js"; // 导入工具函数

const userController = {
  // 获取单个用户(GET /api/users/:id)
  getUserById: async (req, res, next) => {
    try {
      const userId = req.params.id;
      const user = await userService.getUser(userId); // 调用服务层方法
      if (!user) {
        return res.status(404).json({ message: "User not found" });
      }
      // 使用工具函数格式化日期
      user.createdAt = formatDate(user.createdAt);
      res.json(user); // 返回JSON响应
    } catch (err) {
      next(err); // 将错误传递给错误处理中间件
    }
  },

  // 创建用户(POST /api/users)
  createUser: async (req, res, next) => {
    try {
      const userData = req.body;
      const newUser = await userService.createUser(userData); // 调用服务层方法
      res.status(201).json(newUser); // 返回创建的用户(201状态码)
    } catch (err) {
      next(err);
    }
  },
};

export default userController; // 导出控制器实例

3.5 服务模块(src/services/userService.js)

处理业务逻辑(如数据验证、数据库交互),不直接接触请求/响应:

javascript
// src/services/userService.js
// 模拟数据库(实际项目中可替换为MongoDB/MySQL查询)
const users = [
  { id: "1", name: "Alice", age: 25, createdAt: new Date() },
  { id: "2", name: "Bob", age: 30, createdAt: new Date() },
];

const userService = {
  // 获取用户(业务逻辑:根据ID查找用户)
  getUser: async (userId) => {
    return users.find((user) => user.id === userId);
  },

  // 创建用户(业务逻辑:验证数据、添加用户)
  createUser: async (userData) => {
    // 模拟数据验证(实际项目中可使用Joi/Yup)
    if (!userData.name || !userData.age) {
      throw new Error("Name and age are required");
    }
    const newUser = {
      id: String(users.length + 1),
      ...userData,
      createdAt: new Date(),
    };
    users.push(newUser);
    return newUser;
  },
};

export default userService; // 导出服务实例

3.6 工具模块(src/utils/formatDate.js)

定义通用工具函数,可被多个模块导入使用:

javascript
// src/utils/formatDate.js
/**
 * 格式化日期(如:2024-05-20 14:30:00)
 * @param {Date} date - 日期对象
 * @returns {string} 格式化后的日期字符串
 */
export function formatDate(date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

3.7 错误处理中间件(src/middlewares/errorHandler.js)

处理全局错误(如 404、500 错误),返回统一的错误响应:

javascript
// src/middlewares/errorHandler.js
const errorHandler = (err, req, res, next) => {
  console.error("Error:", err.message); // 打印错误日志(实际项目中可使用日志工具如winston)
  // 统一错误响应格式
  res.status(err.statusCode || 500).json({
    message: err.message || "Internal Server Error",
    errors: err.errors || [],
  });
};

export default errorHandler;

4、测试项目

4.1 运行项目

bash
npm start # 或 npm run dev(热更新)

4.2 测试接口

使用 Postman 或浏览器测试以下接口:

  • 获取用户GET http://localhost:3000/api/users/1 响应:
json
{
  "id": "1",
  "name": "Alice",
  "age": 25,
  "createdAt": "2024-05-20 14:30:00"
}
  • 创建用户POST http://localhost:3000/api/users(请求体为{"name": "Charlie", "age": 28}) 响应(201 状态码):
json
{
  "id": "3",
  "name": "Charlie",
  "age": 28,
  "createdAt": "2024-05-20 14:35:00"
}