express + mysql搭建nodejs服务端项目流程系列文章链接:
01、express + mysql搭建nodejs服务端项目流程(一)——使用express-generator创建项目
02、express + mysql搭建nodejs服务端项目流程(二)——添加打印日志winston和mysql相关依赖
03、express + mysql搭建nodejs服务端项目流程(三)——使用joi、crypto-js和jwt等技术完善注册登录接口
本文将记录安装各个依赖包来完善项目架构
1、添加打印日志文件
这里采用的是 winston 日志
具体用法参考:
如何在Express中使用Winston进行日志记录,并配置按日分割文件存储
1.1、安装winston、winston-daily-rotate-file
npm install winston winston-daily-rotate-file
1.2、配置打印日志格式
在项目的根目录添加一个配置打印日志格式的文件logger.js
const winston = require('winston');
require('winston-daily-rotate-file');
const { combine, timestamp, printf, colorize,label } = winston.format;
const path = require('path');
const env = process.env.NODE_ENV || 'development';
// 自定义日志格式
const logFormat = printf( (info) => {
return `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`;
});
// 创建 Logger 实例
const logger = winston.createLogger({
level: env === 'production' ? 'info' : 'debug', // 设置日志级别(error, warn, info, verbose, debug, silly)
format: combine(
label({ label: path.basename(process.mainModule.filename) }),
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), // 添加时间戳
logFormat // 应用自定义格式
),
transports: [
// 控制台输出(带颜色)
new winston.transports.Console({
format: combine(colorize(), logFormat)
}),
// 按日分割文件
new winston.transports.DailyRotateFile({
dirname: 'logs', // 日志目录
filename: 'app-%DATE%.log', // 文件名(%DATE% 自动替换为日期)
datePattern: 'YYYY-MM-DD', // 日期格式
zippedArchive: true, // 压缩旧日志
maxSize: '20m', // 单个文件最大大小
maxFiles: '7d' // 保留最近7天的日志
})
],
// 异常处理(可选)
exceptionHandlers: [
new winston.transports.File({ filename: 'logs/exceptions.log' })
],
// Promise拒绝处理(可选)
rejectionHandlers: [
new winston.transports.File({ filename: 'logs/rejections.log' })
]
});
module.exports = logger;
1.3、修改 app.js 的配置
1.3.1、修改项目默认的变量名
将原来自动生成的代码中,引入的morgan 的变量名改成morgan
var logger = require('morgan');
改成:
const morgan = require('morgan')
相应的也要把
app.use(logger('dev'));
改成:
app.use(morgan('dev'));
1.3.2、引入 logger.js 文件
const logger = require('./logger')
1.3.3、修改错误处理
将错误打印到日志上
// 处理非404的错误(throw 出来的错误)
const _errorHandler = (err, req, res, next) => {
const errorMsg = err instanceof Error ? err.message : err
logger.error(`${req.method} ${req.originalUrl} ` + errorMsg)
res.status(err.status || 500).json({
code: -1,
success: false,
message: errorMsg,
data: null
})
}
app.use(_errorHandler)
1.3.4、测试日志是否打印成功
请求一个不存在的路由,如: http://localhost:3000/hello
打开日志文件,看到这条错误被打印出来就成功了。
2、操作MySql数据库
2.1、安装依赖
npm install mysql2 knex dotenv
- mysql2: MySQL 驱动(支持 Promise)
- knex: SQL 查询构建器(支持事务、链式调用)
- dotenv: 环境变量管理(用于保护数据库配置)
- 为什么使用Knex: 在 为什么使用Knex 做为 Express 中操作 MySQL 数据库的方案?中做了详细记录,这里就不记录了。
2.2、配置 MySQL 连接
2.2.1、 创建 .env
文件
在项目根目录新建 .env
,配置数据库信息:
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=your_database
DB_PORT=3306
2.2.2、 创建 Knex 配置文件
在项目根目录新建 knexfile.js
:
require('dotenv').config();
module.exports = {
development: {
client: 'mysql2',
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT
},
migrations: {
directory: './db/migrations' // 迁移文件目录
},
seeds: {
directory: './db/seeds' // 种子数据目录
}
}
};
2.2.3、 初始化 Knex 实例
在项目根目录新建db目录,在db目录下新建 db.js
文件:
const knex = require('knex');
const config = require('../knexfile');
const db = knex(config.development);
module.exports = db;
在更多关于Knex的使用方式在下面的文章中有记录:
NodeJS 的Express 中集成 MySQL 和 Knex 的完整指南(一)
NodeJS 的Express 中集成 MySQL 和 Knex 的完整指南(二)
2.3、编写简单注册接口,往mysql数据库中添加数据
实现步骤:
- 建表、创建用户models和业务处理类
- 检测表单数据是否合法
- 检测用户名是否被占用
- 插入新用户
2.3.1、 在本地数据库中新建users表,如下:
2.3.2、新建用户模型和控制器,编写用户简单注册接口
在根目录下创建controllers目录,在controllers目录下再创建models目录,在models目录下创建base.js文件
const db = require('../../db/db');
class Base {
constructor(tableName) {
this.tableName = tableName;
}
// 新增
insert(params) {
return db(this.tableName).insert(params);
}
// 删除
delete(id) {
return db(this.tableName).where('id', '=', id).del();
}
// 更改
update(id, params) {
return db(this.tableName).where('id', '=', id).update(params);
}
// 查找
all() {
return db(this.tableName).select();
}
// 根据主键id查询指定记录
findRecordById(id) {
return db(this.tableName).where('id',id).select();
}
}
module.exports = Base;
在models目录下创建user.js文件
const Base = require('./base');
const db = require('../../db/db');
class User extends Base {
constructor(tableName = 'users'){
super(tableName);
}
/**
* 根据账号查询该用户信息
* @param {*} account
*/
findUserByAccount(account){
return db(this.tableName).select().where({account: account}).first();
}
}
module.exports = new User();
在controllers目录下创建user.js文件
const userInfo = require('./models/user');
const logger = require('../logger')
const userController = {
/**
* 处理post请求过来的注册业务
* @param {*} req
* @param {*} res
* @param {*} next
*/
regUser: async (req, res, next) => {
try {
const { account, pwd, nickName } = req.body;
// 判断数据是否合法
if (!account) {
throw '账号不能为空!'
}
if (!pwd) {
throw '密码不能为空!'
}
if (!nickName) {
throw '用户名不能为空!'
}
let dbUser = await userInfo.findUserByAccount(account)
if (dbUser && Object.keys(dbUser).length > 0) {
throw '当前账号已存在,不能重复注册!'
}
let insertData = {
account: account,
pwd: pwd,
name: nickName
}
let insertResult = await userInfo.insert(insertData);
logger.info('用户注册时,插入数据库的结果,result = 【' + insertResult + '】')
res.json({
code: 200,
message: `账号${account}注册成功!`,
data: null
})
} catch (error) {
// 通过next,传给应用级别的错误处理方法进行统一处理
next(error)
}
}
}
module.exports = userController
在routes目录下的users.js的文件中添加用户注册接口:
/** 用户注册接口 */
router.post('/regUser',userController.regUser);