- 发布于
Node.js后端开发实战:Express框架与RESTful API设计
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
Node.js后端开发实战:Express框架与RESTful API设计
Node.js为前端开发者提供了进入后端开发的绝佳机会。本文将分享Node.js后端开发的实战经验,涵盖Express框架、API设计、数据库集成等核心技术。
Express框架基础
项目结构与配置
// 项目结构
/*
backend/
├── src/
│ ├── controllers/
│ │ ├── authController.js
│ │ ├── userController.js
│ │ └── productController.js
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── validation.js
│ │ └── errorHandler.js
│ ├── models/
│ │ ├── User.js
│ │ └── Product.js
│ ├── routes/
│ │ ├── auth.js
│ │ ├── users.js
│ │ └── products.js
│ ├── services/
│ │ ├── authService.js
│ │ └── emailService.js
│ ├── utils/
│ │ ├── database.js
│ │ ├── logger.js
│ │ └── helpers.js
│ ├── config/
│ │ └── index.js
│ └── app.js
├── tests/
├── package.json
└── server.js
*/
// package.json
{
"name": "nodejs-backend-api",
"version": "1.0.0",
"description": "Node.js RESTful API with Express",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.5.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"express-rate-limit": "^6.10.0",
"express-validator": "^7.0.1",
"multer": "^1.4.5",
"nodemailer": "^6.9.4",
"winston": "^3.10.0",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.6.2",
"supertest": "^6.3.3",
"eslint": "^8.47.0"
}
}
// src/config/index.js
require('dotenv').config();
const config = {
// 服务器配置
server: {
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development',
},
// 数据库配置
database: {
uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp',
options: {
useNewUrlParser: true,
useUnifiedTopology: true,
}
},
// JWT配置
jwt: {
secret: process.env.JWT_SECRET || 'your-secret-key',
expiresIn: process.env.JWT_EXPIRES_IN || '7d',
refreshSecret: process.env.JWT_REFRESH_SECRET || 'refresh-secret',
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d',
},
// 邮件配置
email: {
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT || 587,
secure: process.env.EMAIL_SECURE === 'true',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
}
},
// 文件上传配置
upload: {
maxSize: parseInt(process.env.MAX_FILE_SIZE) || 5 * 1024 * 1024, // 5MB
allowedTypes: ['image/jpeg', 'image/png', 'image/gif'],
destination: process.env.UPLOAD_PATH || './uploads',
},
// 安全配置
security: {
bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS) || 12,
rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW) || 15 * 60 * 1000, // 15分钟
rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX) || 100,
}
};
module.exports = config;
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const config = require('./config');
const logger = require('./utils/logger');
const errorHandler = require('./middleware/errorHandler');
const connectDB = require('./utils/database');
// 路由导入
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const productRoutes = require('./routes/products');
class App {
constructor() {
this.app = express();
this.initializeDatabase();
this.initializeMiddlewares();
this.initializeRoutes();
this.initializeErrorHandling();
}
async initializeDatabase() {
try {
await connectDB();
logger.info('Database connected successfully');
} catch (error) {
logger.error('Database connection failed:', error);
process.exit(1);
}
}
initializeMiddlewares() {
// 安全中间件
this.app.use(helmet());
// CORS配置
this.app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true,
}));
// 请求限制
const limiter = rateLimit({
windowMs: config.security.rateLimitWindow,
max: config.security.rateLimitMax,
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
});
this.app.use('/api/', limiter);
// 请求解析
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 静态文件服务
this.app.use('/uploads', express.static('uploads'));
// 请求日志
this.app.use((req, res, next) => {
logger.info(`${req.method} ${req.path} - ${req.ip}`);
next();
});
}
initializeRoutes() {
// API路由
this.app.use('/api/auth', authRoutes);
this.app.use('/api/users', userRoutes);
this.app.use('/api/products', productRoutes);
// 健康检查
this.app.get('/health', (req, res) => {
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: config.server.env,
});
});
// 404处理
this.app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: 'Route not found',
path: req.originalUrl,
});
});
}
initializeErrorHandling() {
this.app.use(errorHandler);
}
listen() {
const port = config.server.port;
this.app.listen(port, () => {
logger.info(`Server running on port ${port} in ${config.server.env} mode`);
});
}
}
module.exports = App;
// server.js
const App = require('./src/app');
const app = new App();
app.listen();
// 优雅关闭
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
process.exit(0);
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
process.exit(0);
});
中间件开发
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const config = require('../config');
const User = require('../models/User');
const logger = require('../utils/logger');
// JWT认证中间件
const authenticateToken = async (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
}
const decoded = jwt.verify(token, config.jwt.secret);
const user = await User.findById(decoded.userId).select('-password');
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid token - user not found'
});
}
if (!user.isActive) {
return res.status(401).json({
success: false,
message: 'Account is deactivated'
});
}
req.user = user;
next();
} catch (error) {
logger.error('Authentication error:', error);
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
success: false,
message: 'Invalid token'
});
}
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: 'Token expired'
});
}
res.status(500).json({
success: false,
message: 'Authentication failed'
});
}
};
// 角色权限中间件
const requireRole = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: 'Insufficient permissions'
});
}
next();
};
};
// 资源所有权验证
const requireOwnership = (resourceModel, resourceIdParam = 'id') => {
return async (req, res, next) => {
try {
const resourceId = req.params[resourceIdParam];
const resource = await resourceModel.findById(resourceId);
if (!resource) {
return res.status(404).json({
success: false,
message: 'Resource not found'
});
}
// 检查资源所有权或管理员权限
if (resource.userId.toString() !== req.user._id.toString() &&
req.user.role !== 'admin') {
return res.status(403).json({
success: false,
message: 'Access denied - not resource owner'
});
}
req.resource = resource;
next();
} catch (error) {
logger.error('Ownership check error:', error);
res.status(500).json({
success: false,
message: 'Authorization check failed'
});
}
};
};
// API密钥认证中间件
const authenticateApiKey = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({
success: false,
message: 'API key required'
});
}
// 这里应该从数据库验证API密钥
const validApiKeys = process.env.VALID_API_KEYS?.split(',') || [];
if (!validApiKeys.includes(apiKey)) {
return res.status(401).json({
success: false,
message: 'Invalid API key'
});
}
next();
};
module.exports = {
authenticateToken,
requireRole,
requireOwnership,
authenticateApiKey
};
// src/middleware/validation.js
const { body, param, query, validationResult } = require('express-validator');
// 验证结果处理中间件
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array().map(error => ({
field: error.path,
message: error.msg,
value: error.value
}))
});
}
next();
};
// 用户注册验证
const validateUserRegistration = [
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Valid email is required'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
.withMessage('Password must contain uppercase, lowercase, number and special character'),
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('Name must be between 2 and 50 characters'),
body('phone')
.optional()
.isMobilePhone()
.withMessage('Valid phone number is required'),
handleValidationErrors
];
// 用户登录验证
const validateUserLogin = [
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Valid email is required'),
body('password')
.notEmpty()
.withMessage('Password is required'),
handleValidationErrors
];
// 产品创建验证
const validateProductCreation = [
body('name')
.trim()
.isLength({ min: 2, max: 100 })
.withMessage('Product name must be between 2 and 100 characters'),
body('description')
.trim()
.isLength({ min: 10, max: 1000 })
.withMessage('Description must be between 10 and 1000 characters'),
body('price')
.isFloat({ min: 0 })
.withMessage('Price must be a positive number'),
body('category')
.trim()
.notEmpty()
.withMessage('Category is required'),
body('stock')
.optional()
.isInt({ min: 0 })
.withMessage('Stock must be a non-negative integer'),
handleValidationErrors
];
// ID参数验证
const validateObjectId = (paramName = 'id') => [
param(paramName)
.isMongoId()
.withMessage('Invalid ID format'),
handleValidationErrors
];
// 分页参数验证
const validatePagination = [
query('page')
.optional()
.isInt({ min: 1 })
.withMessage('Page must be a positive integer'),
query('limit')
.optional()
.isInt({ min: 1, max: 100 })
.withMessage('Limit must be between 1 and 100'),
query('sort')
.optional()
.isIn(['createdAt', '-createdAt', 'name', '-name', 'price', '-price'])
.withMessage('Invalid sort field'),
handleValidationErrors
];
module.exports = {
handleValidationErrors,
validateUserRegistration,
validateUserLogin,
validateProductCreation,
validateObjectId,
validatePagination
};
// src/middleware/errorHandler.js
const logger = require('../utils/logger');
const config = require('../config');
// 自定义错误类
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
Error.captureStackTrace(this, this.constructor);
}
}
// 处理Mongoose验证错误
const handleValidationError = (error) => {
const errors = Object.values(error.errors).map(err => err.message);
const message = `Invalid input data: ${errors.join('. ')}`;
return new AppError(message, 400);
};
// 处理Mongoose重复键错误
const handleDuplicateKeyError = (error) => {
const field = Object.keys(error.keyValue)[0];
const value = error.keyValue[field];
const message = `${field} '${value}' already exists`;
return new AppError(message, 400);
};
// 处理Mongoose转换错误
const handleCastError = (error) => {
const message = `Invalid ${error.path}: ${error.value}`;
return new AppError(message, 400);
};
// 全局错误处理中间件
const errorHandler = (error, req, res, next) => {
let err = { ...error };
err.message = error.message;
// 记录错误日志
logger.error('Error occurred:', {
message: error.message,
stack: error.stack,
url: req.originalUrl,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent')
});
// Mongoose错误处理
if (error.name === 'ValidationError') {
err = handleValidationError(error);
}
if (error.code === 11000) {
err = handleDuplicateKeyError(error);
}
if (error.name === 'CastError') {
err = handleCastError(error);
}
// JWT错误处理
if (error.name === 'JsonWebTokenError') {
err = new AppError('Invalid token', 401);
}
if (error.name === 'TokenExpiredError') {
err = new AppError('Token expired', 401);
}
// 发送错误响应
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Something went wrong';
const response = {
success: false,
message,
...(config.server.env === 'development' && {
error: error.message,
stack: error.stack
})
};
res.status(statusCode).json(response);
};
// 404错误处理
const notFound = (req, res, next) => {
const error = new AppError(`Route ${req.originalUrl} not found`, 404);
next(error);
};
// 异步错误捕获包装器
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
module.exports = {
AppError,
errorHandler,
notFound,
asyncHandler
};
数据库集成
MongoDB与Mongoose
// src/utils/database.js
const mongoose = require('mongoose');
const config = require('../config');
const logger = require('./logger');
// 数据库连接
const connectDB = async () => {
try {
const conn = await mongoose.connect(config.database.uri, config.database.options);
logger.info(`MongoDB Connected: ${conn.connection.host}`);
// 监听连接事件
mongoose.connection.on('error', (err) => {
logger.error('MongoDB connection error:', err);
});
mongoose.connection.on('disconnected', () => {
logger.warn('MongoDB disconnected');
});
// 优雅关闭
process.on('SIGINT', async () => {
await mongoose.connection.close();
logger.info('MongoDB connection closed through app termination');
process.exit(0);
});
return conn;
} catch (error) {
logger.error('Database connection failed:', error);
throw error;
}
};
module.exports = connectDB;
// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('../config');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
maxlength: [50, 'Name cannot exceed 50 characters']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Invalid email format']
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [8, 'Password must be at least 8 characters'],
select: false // 默认不返回密码字段
},
phone: {
type: String,
match: [/^[\+]?[1-9][\d]{0,15}$/, 'Invalid phone number format']
},
avatar: {
type: String,
default: null
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
},
isActive: {
type: Boolean,
default: true
},
isEmailVerified: {
type: Boolean,
default: false
},
emailVerificationToken: String,
emailVerificationExpires: Date,
passwordResetToken: String,
passwordResetExpires: Date,
lastLogin: Date,
loginAttempts: {
type: Number,
default: 0
},
lockUntil: Date,
preferences: {
language: {
type: String,
default: 'en'
},
timezone: {
type: String,
default: 'UTC'
},
notifications: {
email: { type: Boolean, default: true },
push: { type: Boolean, default: true }
}
}
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// 索引
userSchema.index({ email: 1 });
userSchema.index({ createdAt: -1 });
userSchema.index({ isActive: 1, role: 1 });
// 虚拟字段
userSchema.virtual('isLocked').get(function() {
return !!(this.lockUntil && this.lockUntil > Date.now());
});
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(config.security.bcryptRounds);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// 实例方法:验证密码
userSchema.methods.comparePassword = async function(candidatePassword) {
if (!this.password) return false;
return bcrypt.compare(candidatePassword, this.password);
};
// 实例方法:生成JWT令牌
userSchema.methods.generateAuthToken = function() {
const payload = {
userId: this._id,
email: this.email,
role: this.role
};
return jwt.sign(payload, config.jwt.secret, {
expiresIn: config.jwt.expiresIn
});
};
// 实例方法:生成刷新令牌
userSchema.methods.generateRefreshToken = function() {
const payload = {
userId: this._id,
type: 'refresh'
};
return jwt.sign(payload, config.jwt.refreshSecret, {
expiresIn: config.jwt.refreshExpiresIn
});
};
// 实例方法:处理登录失败
userSchema.methods.incLoginAttempts = function() {
// 如果之前有锁定且已过期,重置
if (this.lockUntil && this.lockUntil < Date.now()) {
return this.updateOne({
$unset: { lockUntil: 1 },
$set: { loginAttempts: 1 }
});
}
const updates = { $inc: { loginAttempts: 1 } };
// 如果达到最大尝试次数且未锁定,则锁定账户
if (this.loginAttempts + 1 >= 5 && !this.isLocked) {
updates.$set = { lockUntil: Date.now() + 2 * 60 * 60 * 1000 }; // 锁定2小时
}
return this.updateOne(updates);
};
// 实例方法:重置登录尝试
userSchema.methods.resetLoginAttempts = function() {
return this.updateOne({
$unset: { loginAttempts: 1, lockUntil: 1 },
$set: { lastLogin: Date.now() }
});
};
// 静态方法:查找活跃用户
userSchema.statics.findActiveUsers = function(filter = {}) {
return this.find({ ...filter, isActive: true });
};
// 静态方法:按角色查找用户
userSchema.statics.findByRole = function(role) {
return this.find({ role, isActive: true });
};
module.exports = mongoose.model('User', userSchema);
// src/models/Product.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Product name is required'],
trim: true,
maxlength: [100, 'Product name cannot exceed 100 characters']
},
description: {
type: String,
required: [true, 'Product description is required'],
maxlength: [1000, 'Description cannot exceed 1000 characters']
},
price: {
type: Number,
required: [true, 'Product price is required'],
min: [0, 'Price cannot be negative']
},
category: {
type: String,
required: [true, 'Product category is required'],
enum: ['electronics', 'clothing', 'books', 'home', 'sports', 'other']
},
stock: {
type: Number,
default: 0,
min: [0, 'Stock cannot be negative']
},
images: [{
url: String,
alt: String,
isPrimary: { type: Boolean, default: false }
}],
tags: [String],
specifications: {
type: Map,
of: String
},
ratings: {
average: { type: Number, default: 0, min: 0, max: 5 },
count: { type: Number, default: 0 }
},
isActive: {
type: Boolean,
default: true
},
isFeatured: {
type: Boolean,
default: false
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// 索引
productSchema.index({ name: 'text', description: 'text' });
productSchema.index({ category: 1, isActive: 1 });
productSchema.index({ price: 1 });
productSchema.index({ 'ratings.average': -1 });
productSchema.index({ createdAt: -1 });
productSchema.index({ userId: 1 });
// 虚拟字段
productSchema.virtual('isInStock').get(function() {
return this.stock > 0;
});
productSchema.virtual('primaryImage').get(function() {
const primary = this.images.find(img => img.isPrimary);
return primary || this.images[0] || null;
});
// 静态方法:搜索产品
productSchema.statics.search = function(query, options = {}) {
const {
category,
minPrice,
maxPrice,
inStock,
featured,
sort = '-createdAt',
page = 1,
limit = 10
} = options;
const filter = { isActive: true };
if (query) {
filter.$text = { $search: query };
}
if (category) {
filter.category = category;
}
if (minPrice !== undefined || maxPrice !== undefined) {
filter.price = {};
if (minPrice !== undefined) filter.price.$gte = minPrice;
if (maxPrice !== undefined) filter.price.$lte = maxPrice;
}
if (inStock) {
filter.stock = { $gt: 0 };
}
if (featured) {
filter.isFeatured = true;
}
const skip = (page - 1) * limit;
return this.find(filter)
.populate('userId', 'name email')
.sort(sort)
.skip(skip)
.limit(limit);
};
// 实例方法:更新评分
productSchema.methods.updateRating = async function(newRating) {
const totalRating = this.ratings.average * this.ratings.count + newRating;
this.ratings.count += 1;
this.ratings.average = totalRating / this.ratings.count;
return this.save();
};
module.exports = mongoose.model('Product', productSchema);
RESTful API设计
控制器实现
// src/controllers/userController.js
const User = require('../models/User');
const { AppError, asyncHandler } = require('../middleware/errorHandler');
const logger = require('../utils/logger');
class UserController {
// 获取用户列表
getUsers = asyncHandler(async (req, res) => {
const {
page = 1,
limit = 10,
sort = '-createdAt',
role,
isActive,
search
} = req.query;
const filter = {};
if (role) filter.role = role;
if (isActive !== undefined) filter.isActive = isActive === 'true';
if (search) {
filter.$or = [
{ name: { $regex: search, $options: 'i' } },
{ email: { $regex: search, $options: 'i' } }
];
}
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find(filter)
.select('-password -emailVerificationToken -passwordResetToken')
.sort(sort)
.skip(skip)
.limit(parseInt(limit)),
User.countDocuments(filter)
]);
res.status(200).json({
success: true,
data: {
users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
});
// 获取单个用户
getUser = asyncHandler(async (req, res) => {
const { id } = req.params;
const user = await User.findById(id)
.select('-password -emailVerificationToken -passwordResetToken');
if (!user) {
throw new AppError('User not found', 404);
}
res.status(200).json({
success: true,
data: { user }
});
});
// 获取当前用户信息
getProfile = asyncHandler(async (req, res) => {
const user = await User.findById(req.user._id)
.select('-password -emailVerificationToken -passwordResetToken');
res.status(200).json({
success: true,
data: { user }
});
});
// 更新用户信息
updateUser = asyncHandler(async (req, res) => {
const { id } = req.params;
const updates = req.body;
// 移除敏感字段
delete updates.password;
delete updates.role;
delete updates.isActive;
const user = await User.findById(id);
if (!user) {
throw new AppError('User not found', 404);
}
// 检查权限
if (req.user._id.toString() !== id && req.user.role !== 'admin') {
throw new AppError('Access denied', 403);
}
// 检查邮箱唯一性
if (updates.email && updates.email !== user.email) {
const existingUser = await User.findOne({ email: updates.email });
if (existingUser) {
throw new AppError('Email already exists', 400);
}
}
const updatedUser = await User.findByIdAndUpdate(
id,
updates,
{ new: true, runValidators: true }
).select('-password -emailVerificationToken -passwordResetToken');
logger.info(`User updated: ${updatedUser.email}`);
res.status(200).json({
success: true,
message: 'User updated successfully',
data: { user: updatedUser }
});
});
// 删除用户
deleteUser = asyncHandler(async (req, res) => {
const { id } = req.params;
const user = await User.findById(id);
if (!user) {
throw new AppError('User not found', 404);
}
// 检查权限
if (req.user.role !== 'admin') {
throw new AppError('Access denied', 403);
}
// 软删除:设置为非活跃状态
await User.findByIdAndUpdate(id, { isActive: false });
logger.info(`User deactivated: ${user.email}`);
res.status(200).json({
success: true,
message: 'User deleted successfully'
});
});
// 更改用户角色
changeUserRole = asyncHandler(async (req, res) => {
const { id } = req.params;
const { role } = req.body;
if (!['user', 'admin', 'moderator'].includes(role)) {
throw new AppError('Invalid role', 400);
}
const user = await User.findByIdAndUpdate(
id,
{ role },
{ new: true, runValidators: true }
).select('-password -emailVerificationToken -passwordResetToken');
if (!user) {
throw new AppError('User not found', 404);
}
logger.info(`User role changed: ${user.email} -> ${role}`);
res.status(200).json({
success: true,
message: 'User role updated successfully',
data: { user }
});
});
// 获取用户统计信息
getUserStats = asyncHandler(async (req, res) => {
const stats = await User.aggregate([
{
$group: {
_id: null,
totalUsers: { $sum: 1 },
activeUsers: {
$sum: { $cond: [{ $eq: ['$isActive', true] }, 1, 0] }
},
verifiedUsers: {
$sum: { $cond: [{ $eq: ['$isEmailVerified', true] }, 1, 0] }
}
}
},
{
$project: {
_id: 0,
totalUsers: 1,
activeUsers: 1,
verifiedUsers: 1,
inactiveUsers: { $subtract: ['$totalUsers', '$activeUsers'] }
}
}
]);
const roleStats = await User.aggregate([
{ $match: { isActive: true } },
{ $group: { _id: '$role', count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]);
res.status(200).json({
success: true,
data: {
overview: stats[0] || {
totalUsers: 0,
activeUsers: 0,
verifiedUsers: 0,
inactiveUsers: 0
},
roleDistribution: roleStats
}
});
});
}
module.exports = new UserController();
总结
Node.js后端开发的核心要点:
- Express框架:中间件系统、路由设计、错误处理
- 数据库集成:Mongoose ODM、模型设计、查询优化
- API设计:RESTful规范、状态码、响应格式
- 安全防护:认证授权、输入验证、错误处理
- 项目架构:分层设计、模块化、配置管理
Node.js为前端开发者提供了全栈开发的能力,通过合理的架构设计和最佳实践,可以构建出高质量的后端API服务。