前言
在日常的 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!让我们一起打造更完美的开发工具。