Express数据库集成
1. 数据库集成概述
在构建Web应用程序时,数据库是存储、检索和管理数据的关键组件。Express本身不提供数据库集成功能,但它可以轻松地与各种数据库系统集成,包括关系型数据库(如MySQL、PostgreSQL)和NoSQL数据库(如MongoDB、Redis)。
选择合适的数据库取决于多种因素:
- 数据结构和关系复杂性
- 性能需求
- 可扩展性要求
- 开发团队的熟悉程度
- 部署环境限制
2. 与MongoDB集成
MongoDB是一个流行的NoSQL文档数据库,非常适合与Express一起使用。
2.1 安装依赖
npm install mongoose --save
2.2 配置数据库连接
创建config/database.js:
const mongoose = require('mongoose')
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
console.log(`MongoDB Connected: ${conn.connection.host}`)
} catch (error) {
console.error(`Error: ${error.message}`)
process.exit(1)
}
}
module.exports = connectDB
在应用程序中使用:
const express = require('express')
const dotenv = require('dotenv')
const connectDB = require('./config/database')
// 加载环境变量
dotenv.config()
// 连接数据库
connectDB()
const app = express()
// ... 其他配置和路由 ...
const PORT = process.env.PORT || 3000
app.listen(PORT, console.log(`Server running on port ${PORT}`))
2.3 创建数据模型
创建models/User.js:
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, '请添加用户名'],
trim: true
},
email: {
type: String,
required: [true, '请添加邮箱'],
unique: true,
trim: true,
match: [
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
'请输入有效的邮箱地址'
]
},
password: {
type: String,
required: [true, '请添加密码'],
minlength: 6
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('User', userSchema)
2.4 在路由中使用模型
创建controllers/userController.js:
const User = require('../models/User')
// @desc 获取所有用户
// @route GET /api/users
// @access 公开
const getUsers = async (req, res, next) => {
try {
const users = await User.find()
res.status(200).json({
success: true,
count: users.length,
data: users
})
} catch (error) {
res.status(500).json({
success: false,
error: '服务器错误'
})
}
}
// @desc 创建用户
// @route POST /api/users
// @access 公开
const createUser = async (req, res, next) => {
try {
const user = await User.create(req.body)
res.status(201).json({
success: true,
data: user
})
} catch (error) {
res.status(400).json({
success: false,
error: error.message
})
}
}
module.exports = {
getUsers,
createUser
}
创建routes/userRoutes.js:
const express = require('express')
const router = express.Router()
const { getUsers, createUser } = require('../controllers/userController')
router
.route('/')
.get(getUsers)
.post(createUser)
module.exports = router
在应用程序中注册路由:
const express = require('express')
const app = express()
// 解析JSON请求体
app.use(express.json())
// 挂载路由
app.use('/api/users', require('./routes/userRoutes'))
// ... 其他配置 ...
3. 与MySQL集成
MySQL是一个流行的关系型数据库管理系统,适用于需要复杂事务和关系查询的应用程序。
3.1 安装依赖
npm install mysql2 sequelize --save
3.2 配置数据库连接
创建config/database.js:
const Sequelize = require('sequelize')
// 创建Sequelize实例
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: 'mysql',
port: process.env.DB_PORT || 3306,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
)
// 测试连接
sequelize
.authenticate()
.then(() => {
console.log('MySQL连接成功')
})
.catch(err => {
console.error('无法连接到MySQL数据库:', err)
})
module.exports = sequelize
3.3 创建数据模型
创建models/User.js:
const Sequelize = require('sequelize')
const sequelize = require('../config/database')
const User = sequelize.define('User', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
name: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: Sequelize.STRING,
allowNull: false
}
}, {
timestamps: true
})
module.exports = User
3.4 同步数据库模型
const sequelize = require('./config/database')
const User = require('./models/User')
// 同步所有模型
sequelize.sync()
.then(() => {
console.log('数据库模型同步成功')
})
.catch(err => {
console.error('数据库模型同步失败:', err)
})
3.5 在路由中使用模型
const express = require('express')
const router = express.Router()
const User = require('../models/User')
// 获取所有用户
router.get('/', async (req, res) => {
try {
const users = await User.findAll()
res.status(200).json(users)
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// 创建新用户
router.post('/', async (req, res) => {
try {
const user = await User.create(req.body)
res.status(201).json(user)
} catch (error) {
res.status(400).json({ error: error.message })
}
})
module.exports = router
4. 与Redis集成
Redis是一个开源的内存数据结构存储,可用作数据库、缓存和消息代理。
4.1 安装依赖
npm install redis --save
4.2 配置Redis连接
创建config/redis.js:
const redis = require('redis')
const client = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD
})
client.on('connect', () => {
console.log('已连接到Redis服务器')
})
client.on('error', (err) => {
console.error('Redis连接错误:', err)
})
module.exports = client
4.3 使用Redis作为缓存
创建缓存中间件middlewares/cache.js:
const redisClient = require('../config/redis')
const cache = (duration) => {
return (req, res, next) => {
const key = req.originalUrl
redisClient.get(key, (err, data) => {
if (err) throw err
if (data !== null) {
// 命中缓存
res.send(JSON.parse(data))
} else {
// 重写res.send方法,将响应缓存
const originalSend = res.send
res.send = function(body) {
// 缓存响应数据
redisClient.setex(key, duration, body)
originalSend.call(this, body)
}
next()
}
})
}
}
module.exports = cache
在路由中使用缓存中间件:
const express = require('express')
const router = express.Router()
const User = require('../models/User')
const cache = require('../middlewares/cache')
// 获取所有用户(添加5分钟缓存)
router.get('/', cache(300), async (req, res) => {
try {
const users = await User.find()
res.status(200).json({
success: true,
data: users
})
} catch (error) {
res.status(500).json({
success: false,
error: '服务器错误'
})
}
})
module.exports = router
5. 数据库连接池管理
数据库连接池是一组预先创建的数据库连接,这些连接可以被重用,而不是在每次请求时创建和销毁新的连接。
5.1 MongoDB连接池
Mongoose(MongoDB的ODM)默认使用连接池,通常不需要额外配置。但可以根据需要调整连接池大小:
const mongoose = require('mongoose')
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
poolSize: 10, // 最大连接数
serverSelectionTimeoutMS: 5000, // 服务器选择超时
socketTimeoutMS: 45000, // 套接字超时
family: 4 // 使用IPv4,跳过IPv6查找
})
console.log('MongoDB连接成功')
} catch (error) {
console.error('MongoDB连接失败:', error)
process.exit(1)
}
}
5.2 MySQL连接池
使用mysql2包配置连接池:
const mysql = require('mysql2/promise')
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})
module.exports = pool
使用连接池执行查询:
const pool = require('../config/database')
const getUsers = async (req, res) => {
try {
const [rows] = await pool.execute('SELECT * FROM users')
res.json(rows)
} catch (error) {
console.error('查询错误:', error)
res.status(500).json({ error: '服务器错误' })
}
}
6. 事务处理
事务是一组要么全部成功要么全部失败的操作,对于确保数据一致性至关重要。
6.1 MongoDB事务
MongoDB 4.0及以上版本支持事务,但需要使用复制集:
const session = await mongoose.startSession()
session.startTransaction()
try {
// 在事务中执行操作
const user = await User.create([{ name: '张三', email: 'zhangsan@example.com' }], { session })
const order = await Order.create([{ userId: user[0]._id, product: '产品A' }], { session })
// 提交事务
await session.commitTransaction()
res.status(201).json({ user, order })
} catch (error) {
// 回滚事务
await session.abortTransaction()
res.status(400).json({ error: error.message })
} finally {
session.endSession()
}
6.2 MySQL事务
const pool = require('../config/database')
const createUserWithProfile = async (req, res) => {
const connection = await pool.getConnection()
try {
// 开始事务
await connection.beginTransaction()
// 执行SQL语句
const [userResult] = await connection.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
[req.body.name, req.body.email]
)
const [profileResult] = await connection.execute(
'INSERT INTO profiles (user_id, bio) VALUES (?, ?)',
[userResult.insertId, req.body.bio]
)
// 提交事务
await connection.commit()
res.status(201).json({ success: true, userId: userResult.insertId })
} catch (error) {
// 回滚事务
await connection.rollback()
res.status(400).json({ error: error.message })
} finally {
// 释放连接
connection.release()
}
}
7. 数据库迁移和种子
数据库迁移是管理数据库架构变更的过程,而种子是填充初始数据的过程。
7.1 使用Sequelize进行MySQL迁移
安装依赖:
npm install sequelize-cli --save-dev
初始化Sequelize CLI:
npx sequelize-cli init
创建迁移文件:
npx sequelize-cli model:generate --name User --attributes name:string,email:string,password:string
运行迁移:
npx sequelize-cli db:migrate
创建种子文件:
npx sequelize-cli seed:generate --name demo-user
运行种子:
npx sequelize-cli db:seed:all
7.2 使用Mongoose进行MongoDB迁移
对于MongoDB,可以使用migrate-mongo包:
npm install migrate-mongo --save-dev
npx migrate-mongo init
编辑migrate-mongo-config.js配置文件,然后创建迁移:
npx migrate-mongo create create-users-collection
运行迁移:
npx migrate-mongo up
8. 数据库性能优化
8.1 查询优化
- 限制返回字段:只查询需要的字段
- 分页查询:避免一次性返回大量数据
- 索引:为常用查询字段添加索引
- 避免N+1查询问题:使用适当的关联查询
MongoDB中的选择性查询:
// 只返回name和email字段
const users = await User.find({}, 'name email')
// 分页查询
const page = parseInt(req.query.page) || 1
const limit = parseInt(req.query.limit) || 10
const skip = (page - 1) * limit
const users = await User.find()
.skip(skip)
.limit(limit)
MySQL中的选择性查询:
// 只返回name和email字段
const [users] = await pool.execute('SELECT name, email FROM users')
// 分页查询
const page = parseInt(req.query.page) || 1
const limit = parseInt(req.query.limit) || 10
const offset = (page - 1) * limit
const [users] = await pool.execute(
'SELECT * FROM users LIMIT ? OFFSET ?',
[limit, offset]
)
8.2 连接池优化
- 调整连接池大小以匹配应用需求
- 设置适当的连接超时
- 监控连接池使用情况
8.3 缓存策略
- 对频繁访问但不经常变化的数据使用缓存
- 设置合理的缓存过期时间
- 考虑使用Redis等内存数据库进行缓存
9. 数据库安全最佳实践
- 使用环境变量存储凭据:不要在代码中硬编码数据库凭据
- 实施输入验证:防止SQL注入和NoSQL注入攻击
- 使用参数化查询:避免SQL注入
- 限制数据库用户权限:遵循最小权限原则
- 加密敏感数据:如密码、个人身份信息等
- 定期备份数据库:确保数据可恢复性
- 使用ORM或ODM:减少手写SQL的安全风险
- 实施连接池:防止连接耗尽攻击
- 监控数据库访问:检测可疑活动
- 保持数据库软件更新:应用安全补丁和更新
10. 练习项目
创建一个具有以下功能的Express应用程序:
- 与至少两种数据库集成(MongoDB和MySQL)
- 实现完整的CRUD操作
- 使用ORM/ODM(Mongoose和Sequelize)
- 配置连接池
- 实现事务处理
- 设置缓存策略
- 实施数据库迁移和种子
- 优化查询性能
- 应用安全最佳实践
通过这个练习,你将掌握Express与各种数据库集成的技术,能够构建数据驱动的Web应用程序。