mkdir node-mongo-app
cd node-mongo-app
npm init -y
npm install mongodb mongoose express cors dotenv
mongodb
: 官方MongoDB Node.js驱动mongoose
: MongoDB对象建模工具express
: Web应用框架cors
: 跨域资源共享中间件dotenv
: 环境变量管理MONGODB_URI=mongodb://localhost:27017/mydatabase
PORT=3000
const { MongoClient } = require('mongodb');
require('dotenv').config();
let client;
let db;
async function connectToDatabase() {
if (db) return db;
try {
client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
db = client.db();
console.log('Connected to MongoDB');
return db;
} catch (err) {
console.error('MongoDB connection error:', err);
process.exit(1);
}
}
async function closeConnection() {
if (client) {
await client.close();
console.log('MongoDB connection closed');
}
}
module.exports = { connectToDatabase, closeConnection };
const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
// 添加更新时间的钩子
userSchema.pre('save', function(next) {
this.updatedAt = Date.now();
next();
});
module.exports = mongoose.model('User', userSchema);
const mongoose = require('mongoose');
require('dotenv').config();
async function connectMongoose() {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('Mongoose connected to MongoDB');
} catch (err) {
console.error('Mongoose connection error:', err);
process.exit(1);
}
}
module.exports = connectMongoose;
const User = require('../models/User');
class UserService {
// 创建用户
static async createUser(userData) {
try {
const user = new User(userData);
await user.save();
return user;
} catch (err) {
throw err;
}
}
// 获取所有用户
static async getUsers() {
try {
return await User.find({});
} catch (err) {
throw err;
}
}
// 获取单个用户
static async getUserById(id) {
try {
return await User.findById(id);
} catch (err) {
throw err;
}
}
// 更新用户
static async updateUser(id, updateData) {
try {
return await User.findByIdAndUpdate(id, updateData, { new: true });
} catch (err) {
throw err;
}
}
// 删除用户
static async deleteUser(id) {
try {
return await User.findByIdAndDelete(id);
} catch (err) {
throw err;
}
}
}
module.exports = UserService;
const UserService = require('../services/userService');
class UserController {
static async createUser(req, res) {
try {
const user = await UserService.createUser(req.body);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
static async getUsers(req, res) {
try {
const users = await UserService.getUsers();
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
}
static async getUser(req, res) {
try {
const user = await UserService.getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
}
static async updateUser(req, res) {
try {
const user = await UserService.updateUser(req.params.id, req.body);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
static async deleteUser(req, res) {
try {
const user = await UserService.deleteUser(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ message: 'User deleted successfully' });
} catch (err) {
res.status(500).json({ error: err.message });
}
}
}
module.exports = UserController;
const express = require('express');
const cors = require('cors');
const connectMongoose = require('./config/mongoose');
const userRoutes = require('./routes/userRoutes');
require('dotenv').config();
const app = express();
// 中间件
app.use(cors());
app.use(express.json());
// 数据库连接
connectMongoose();
// 路由
app.use('/api/users', userRoutes);
// 健康检查端点
app.get('/health', (req, res) => {
res.status(200).json({ status: 'OK' });
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
const express = require('express');
const UserController = require('../controllers/userController');
const router = express.Router();
router.post('/', UserController.createUser);
router.get('/', UserController.getUsers);
router.get('/:id', UserController.getUser);
router.put('/:id', UserController.updateUser);
router.delete('/:id', UserController.deleteUser);
module.exports = router;
// 在模型定义中添加索引
userSchema.index({ username: 1 }, { unique: true });
userSchema.index({ email: 1 }, { unique: true });
userSchema.index({ createdAt: -1 }); // 按创建时间降序
static async getUserStats() {
try {
return await User.aggregate([
{
$group: {
_id: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" }
},
count: { $sum: 1 }
}
},
{ $sort: { "_id.year": 1, "_id.month": 1 } }
]);
} catch (err) {
throw err;
}
}
static async transferCredits(fromUserId, toUserId, amount) {
const session = await mongoose.startSession();
session.startTransaction();
try {
const fromUser = await User.findById(fromUserId).session(session);
if (fromUser.credits < amount) {
throw new Error('Insufficient credits');
}
const toUser = await User.findById(toUserId).session(session);
fromUser.credits -= amount;
toUser.credits += amount;
await fromUser.save({ session });
await toUser.save({ session });
await session.commitTransaction();
session.endSession();
return { fromUser, toUser };
} catch (err) {
await session.abortTransaction();
session.endSession();
throw err;
}
}
连接池管理:
mongoose.connect(process.env.MONGODB_URI, {
poolSize: 10, // 连接池大小
socketTimeoutMS: 30000,
connectTimeoutMS: 30000
});
批量操作:
// 批量插入
await User.insertMany(usersArray);
// 批量更新
await User.updateMany(
{ status: 'active' },
{ $set: { lastLogin: new Date() } }
);
查询优化:
// 只选择需要的字段
User.find().select('username email');
// 使用游标处理大数据集
const cursor = User.find().cursor();
for (let user = await cursor.next(); user != null; user = await cursor.next()) {
// 处理每个用户
}
输入验证:
const Joi = require('joi');
const userSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{8,30}$')).required()
});
密码哈希:
const bcrypt = require('bcrypt');
const saltRounds = 10;
// 保存前哈希密码
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, saltRounds);
next();
});
查询注入防护:
// 避免直接使用用户输入构建查询
// 错误方式
const query = { username: req.query.username };
// 正确方式 - 使用参数化查询
const query = { username: sanitize(req.query.username) };
const UserService = require('../services/userService');
const User = require('../models/User');
jest.mock('../models/User');
describe('UserService', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('createUser', () => {
it('should create a new user', async () => {
const mockUser = { _id: '1', username: 'test', email: 'test@example.com' };
User.prototype.save.mockResolvedValue(mockUser);
const result = await UserService.createUser(mockUser);
expect(result).toEqual(mockUser);
expect(User.prototype.save).toHaveBeenCalled();
});
});
});
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
describe('User API', () => {
beforeAll(async () => {
await mongoose.connect(process.env.MONGODB_URI_TEST, {
useNewUrlParser: true,
useUnifiedTopology: true
});
});
afterAll(async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const res = await request(app)
.post('/api/users')
.send({
username: 'testuser',
email: 'test@example.com',
password: 'password123'
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('_id');
expect(res.body.username).toBe('testuser');
});
});
});
生产环境配置:
MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/mydatabase?retryWrites=true&w=majority
NODE_ENV=production
PORT=8080
连接重试逻辑:
const mongoose = require('mongoose');
require('dotenv').config();
const connectWithRetry = () => {
return mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
retryWrites: true,
retryReads: true
})
.then(() => console.log('Successfully connected to MongoDB'))
.catch(err => {
console.error('MongoDB connection error:', err);
setTimeout(connectWithRetry, 5000);
});
};
module.exports = connectWithRetry;
使用PM2进行进程管理:
npm install pm2 -g
pm2 start app.js --name "node-mongo-app"
pm2 save
pm2 startup
添加请求日志:
const morgan = require('morgan');
app.use(morgan('combined'));
性能监控:
const { monitorMongoose } = require('mongoose-metrics');
monitorMongoose(mongoose);
错误跟踪:
const Sentry = require('@sentry/node');
Sentry.init({ dsn: process.env.SENTRY_DSN });
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());
本实战指南涵盖了MongoDB与Node.js集成的各个方面,从基础连接到高级特性。关键要点包括:
通过遵循这些实践,您可以构建高性能、可扩展且安全的Node.js应用,充分利用MongoDB的强大功能。