告别重复造轮子!Node+Express+Sequelize+MySQL 一体化脚手架

51 阅读5分钟

前言

在日常的 Node.js 后端开发中,你是否经常遇到这样的场景:每次开始新项目,都要重新配置 Express 框架、连接数据库、设置 Sequelize ORM、编写认证中间件...这些重复性工作不仅耗时耗力,还容易出错。经过多个项目的锤炼,我开发了一款高度封装的 Node.js 服务端脚手架,整合了 Express + Sequelize + MySQL 的最佳实践,今天分享给大家!

基本介绍

利用 express 脚手架搭建,再此基础上利用 sequelize 连接 mysql 数据库,主要分三层:dao 层、model 层、service 层,其中 dao 层负责与数据库打交道,model 层负责定义数据表结构,service 层负责业务逻辑处理。利用 express 框架提供的中间件,如 cors、body-parser、multer 等,实现跨域、文件上传等功能,利用 winston morgan winston-daily-rotate-file 等日志中间件,实现日志记录功能。

脚手架特性

🚀 开箱即用

  • 零配置启动,一行命令快速创建项目结构
  • 内置常用中间件和工具函数
  • 统一的错误处理和日志记录

🔧 最佳实践集成

  • JWT 身份认证体系
  • 安全的密码哈希处理
  • 内置文件上传功能

项目结构

nodeExpress/
|—— bin/
|   └── www.js        # 项目启动文件
├── dao/                # 数据库相关文件
│   └── userDao.js        # 负责与数据库打交道(查询数据库可理解为sql语句)
├── model/   # 模型文件,后续其他表都可在该目录下增加
│   └── userModel.js    # 用户模型(数据库user表字段)
├── db/                # 数据库相关文件
│   ├── dbConnect.js   # 数据库连接文件
│   └── db.js        # 同步数据库模型
|—— files/            # 文件保存目录
|—— logs/             # 日志存放目录
|   └── audit.json     # 日志配置文件
|   └── access.log     # 网络请求日志
|   └── app-xxxx-xx-xx.log      # 某一天的应用日志
|   └── error.log      # 错误日志
|—— middleware/             # 中间件
|   └── checkSessionId.js     # 校验sessionId,实现单点登录
|   └── cors.js     # 跨域设置
|—— public/            # 静态文件目录(express脚手架自带)
├── routes/            # 路由相关文件
│   ├── upload.js      # 文件上传相关路由(api)
│   └── user.js        # 用户相关路由(api)
├── service/            # 业务处理层
│   ├── uploadService.js      # 处理文件上传
│   └── userService.js        # 处理用户相关业务
├── utils/             # 工具相关文件
|   ├── config.js      # 一些配置
│   ├── logger.js      # 日志记录工具
|   └── tools.js        # 一些工具函数
│   └── error.js    # 封住的错误处理
├── views/             # express脚手架自带
├── app.js             # 应用入口文件
├── .env         # 生产环境变量
├── .env.dev           # 开发环境变量
├── .env.test            # 测试环境变量

快速开始

安装使用

# 全局安装脚手架
npm install -g express-seq-generator

# 创建新项目
create-node-express

# 进入项目并安装依赖
cd my-project && npm install

# 启动开发服务器
npm run dev

核心配置示例

// db/dbConnect.js
const { Sequelize } = require("sequelize");
// 创建数据库连接
const sequelize = new Sequelize(
  process.env.DB_NAME,
  process.env.DB_USER,
  process.env.DB_PASSWORD,
  {
    host: process.env.DB_HOST,
    dialect:
      "mysql" /* one of 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql' | 'db2' | 'snowflake' | 'oracle' */,
    logging: false,
    timezone: "+08:00", // 设置时区,如果不设置,数据库存储的时间会是 UTC 时间,导致时间不一致
  }
);

模型定义变得简单

// 定义数据模型
module.exports = sequelize.define(
  "user", // 表名
  {
    /**
     * 定义用户模型的数据结构
     * 包含用户的基本信息字段
     */
    userId: {
      type: DataTypes.UUID, // 数据类型为整数
      allowNull: false, // 不允许为空
      primaryKey: true, // 设置为主键
      defaultValue: DataTypes.UUIDV4, // 默认值为 UUID
    },
    userName: {
      type: DataTypes.STRING, // 数据类型为字符串
      allowNull: false, // 不允许为空
    },
    passWord: {
      type: DataTypes.STRING, // 数据类型为字符串
      allowNull: false, // 不允许为空
    },
    sessionId: {
      type: DataTypes.STRING, // 数据类型为字符串
      allowNull: true, // 允许为空
    },
    created: {
      type: DataTypes.DATE, // 数据类型为日期
      allowNull: false, // 不允许为空
      defaultValue: DataTypes.NOW, // 默认值为当前时间
    },
    updated: {
      type: DataTypes.DATE, // 数据类型为日期
      allowNull: false, // 不允许为空
      defaultValue: DataTypes.NOW, // 默认值为当前时间
    },
  },
  {
    // 定义模型的其他选项
    freezeTableName: true, // 禁止修改表名为复数形式
    timestamps: false, // 启用自动时间戳
    hooks: {
      beforeUpdate: (user) => {
        user.updated = new Date();
      },
    },
  }
);

特色功能

1. 统一错误处理以及响应格式

const { formatResponse } = require("./tools");
// 自定义错误
class ServiceError extends Error {
  /**
   * 构造函数
   * @param {string} message - 错误信息
   * @param {number} code - 错误代码
   */
  constructor(message, code) {
    // 调用父类构造函数,传入错误信息
    super(message);
    // 设置错误代码属性
    this.code = code;
  }
  toResponseJSON() {
    return formatResponse(this.code, this.message, null);
  }
}

// 文件上传错误
/**
 * 上传错误类
 * 继承自ServiceError,用于处理文件上传过程中出现的错误
 */
exports.UploadError = class extends ServiceError {
  /**
   * 创建上传错误实例
   * @param {string} message - 错误信息
   */
  constructor(message) {
    super(message, 413); // 调用父类构造函数,设置错误信息为413状态码
  }
};
// 禁止访问错误
exports.ForbiddenError = class extends ServiceError {
  constructor(message) {
    super(message, 401);
  }
};
// 验证错误
exports.ValidationError = class extends ServiceError {
  constructor(message) {
    super(message, 406);
  }
};
// 无资源错误
exports.NotFoundError = class extends ServiceError {
  constructor() {
    super("not found", 404);
  }
};
// 未知错误
exports.UnknownError = class extends ServiceError {
  constructor() {
    super("server error", 500);
  }
};

module.exports.ServiceError = ServiceError;

// 响应格式处理
module.exports.formatResponse = (code, msg, data) => {
  return {
    code,
    msg,
    data,
  };
};

2. 全局日志收集

// app.js
// error handler
app.use(function (err, req, res, next) {
  logger.error("Unhandled error:", {
    error: err.message,
    stack: err.stack,
    request: {
      method: req.method,
      url: req.url,
      body: req.body,
    },
  });
  if (err.name === "UnauthorizedError") {
    res.send(
      new ForbiddenError("未登录或者登录过期,请重新登录").toResponseJSON()
    );
  } else if (err instanceof ServiceError) {
    res.send(err.toResponseJSON());
  } else {
    res.send(new UnknownError().toResponseJSON());
  }
});


// logger.js
const winston = require("winston");
const DailyRotateFile = require("winston-daily-rotate-file");
const { combine, timestamp, printf, colorize, errors } = winston.format;

// 自定义日志格式(控制台)
const consoleFormat = printf(({ level, message, timestamp, stack }) => {
  let log = `${timestamp} [${level}]: ${message}`;
  if (stack) log += `\n${stack}`; // 错误堆栈
  return log;
});

// 创建 logger
const logger = winston.createLogger({
  level: "info", // 默认日志级别
  format: combine(
    timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
    errors({ stack: true }), // 捕获错误堆栈
    winston.format.json() // 生产环境用 JSON
  ),
  transports: [
    // 开发环境:控制台输出(彩色+简洁格式)
    new winston.transports.Console({
      format: combine(colorize(), consoleFormat),
    }),
    // 生产环境:按天轮换日志文件
    new DailyRotateFile({
      filename: "logs/app-%DATE%.log",
      datePattern: "YYYY-MM-DD",
      zippedArchive: true, // 自动压缩旧日志
      maxSize: "20m", // 单个文件最大 20MB
      maxFiles: "30d", // 保留 30 天日志
    }),
    // 单独记录错误日志
    new winston.transports.File({
      filename: "logs/error.log",
      level: "error", // 只记录 error 及以上级别
    }),
  ],
});

// 如果是生产环境,关闭控制台输出
if (process.env.NODE_ENV === "prod") {
  logger.remove(winston.transports.Console);
}
module.exports = logger;

总结

  • ✅ 减少重复配置时间
  • ✅ 统一团队开发规范
  • ✅ 降低新手入门门槛

未来规划

  • 增加 GraphQL 支持
  • 集成更多数据库支持(MongoDB、PostgreSQL)
  • 添加微服务架构支持
  • 开发可视化配置界面

结语

如果你也在为 Node.js 后端项目的重复配置而烦恼,不妨试试这个脚手架。相信它能显著提升你的开发效率,让你更专注于业务逻辑的实现。​​项目地址​​: GitHub 链接 欢迎 Star ⭐ 和提交 Issue/Feature Request!让我们一起打造更完美的开发工具。