- 发布于
Node.js后端API开发:构建现代化的RESTful服务
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
Node.js后端API开发:构建现代化的RESTful服务
Node.js凭借其事件驱动和非阻塞I/O特性,成为构建高性能后端API的热门选择。本文将详细介绍使用Node.js开发现代化API服务的完整流程。
项目架构和环境搭建
项目结构设计
{
"name": "nodejs-api-server",
"version": "1.0.0",
"description": "Modern Node.js API Server",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"build": "babel src -d dist",
"docker:build": "docker build -t nodejs-api .",
"docker:run": "docker run -p 3000:3000 nodejs-api"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.5.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"joi": "^17.9.2",
"helmet": "^7.0.0",
"cors": "^2.8.5",
"compression": "^1.7.4",
"morgan": "^1.10.0",
"winston": "^3.10.0",
"dotenv": "^16.3.1",
"express-rate-limit": "^6.10.0",
"express-validator": "^7.0.1",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.4",
"redis": "^4.6.7",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.6.2",
"supertest": "^6.3.3",
"eslint": "^8.47.0",
"prettier": "^3.0.2",
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9"
}
}
项目目录结构
nodejs-api-server/
├── src/
│ ├── controllers/
│ │ ├── authController.js
│ │ ├── userController.js
│ │ └── postController.js
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── validation.js
│ │ ├── errorHandler.js
│ │ └── rateLimiter.js
│ ├── models/
│ │ ├── User.js
│ │ └── Post.js
│ ├── routes/
│ │ ├── auth.js
│ │ ├── users.js
│ │ └── posts.js
│ ├── services/
│ │ ├── authService.js
│ │ ├── emailService.js
│ │ └── cacheService.js
│ ├── utils/
│ │ ├── logger.js
│ │ ├── database.js
│ │ └── helpers.js
│ ├── config/
│ │ ├── database.js
│ │ ├── redis.js
│ │ └── swagger.js
│ └── app.js
├── tests/
├── docs/
├── .env.example
├── .gitignore
├── Dockerfile
└── docker-compose.yml
核心应用配置
// src/app.js - 应用主入口
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./config/swagger');
// 导入路由
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
// 导入中间件
const errorHandler = require('./middleware/errorHandler');
const logger = require('./utils/logger');
// 加载环境变量
require('dotenv').config();
const app = express();
// 数据库连接
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => logger.info('MongoDB connected successfully'))
.catch(err => logger.error('MongoDB connection error:', err));
// 基础中间件
app.use(helmet()); // 安全头
app.use(cors()); // 跨域支持
app.use(compression()); // 响应压缩
app.use(morgan('combined', { stream: { write: message => logger.info(message.trim()) } }));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 限制每个IP 100个请求
message: {
error: 'Too many requests from this IP, please try again later.'
}
});
app.use('/api/', limiter);
// API文档
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// 健康检查
app.get('/health', (req, res) => {
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV || 'development'
});
});
// API路由
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
// 404处理
app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: 'API endpoint not found'
});
});
// 错误处理中间件
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
logger.info(`API Documentation available at http://localhost:${PORT}/api-docs`);
});
module.exports = app;
数据模型设计
// src/models/User.js - 用户模型
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Username is required'],
unique: true,
trim: true,
minlength: [3, 'Username must be at least 3 characters'],
maxlength: [30, 'Username cannot exceed 30 characters'],
match: [/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
trim: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Please enter a valid email']
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [8, 'Password must be at least 8 characters'],
select: false // 默认不返回密码字段
},
firstName: {
type: String,
required: [true, 'First name is required'],
trim: true,
maxlength: [50, 'First name cannot exceed 50 characters']
},
lastName: {
type: String,
required: [true, 'Last name is required'],
trim: true,
maxlength: [50, 'Last name cannot exceed 50 characters']
},
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,
passwordResetToken: String,
passwordResetExpires: Date,
lastLogin: Date,
loginAttempts: {
type: Number,
default: 0
},
lockUntil: Date
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// 虚拟字段
userSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
userSchema.virtual('isLocked').get(function() {
return !!(this.lockUntil && this.lockUntil > Date.now());
});
// 索引
userSchema.index({ email: 1 });
userSchema.index({ username: 1 });
userSchema.index({ createdAt: -1 });
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(12);
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);
};
userSchema.methods.generateAuthToken = function() {
const payload = {
id: this._id,
username: this.username,
email: this.email,
role: this.role
};
return jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE || '7d'
});
};
userSchema.methods.generateRefreshToken = function() {
const payload = {
id: this._id,
type: 'refresh'
};
return jwt.sign(payload, process.env.JWT_REFRESH_SECRET, {
expiresIn: process.env.JWT_REFRESH_EXPIRE || '30d'
});
};
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 }
});
};
// 静态方法
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email: email.toLowerCase() });
};
userSchema.statics.findByUsername = function(username) {
return this.findOne({ username: username });
};
module.exports = mongoose.model('User', userSchema);
认证和授权系统
// src/controllers/authController.js - 认证控制器
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { validationResult } = require('express-validator');
const emailService = require('../services/emailService');
const cacheService = require('../services/cacheService');
const logger = require('../utils/logger');
class AuthController {
// 用户注册
async register(req, res, next) {
try {
// 验证输入
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
const { username, email, password, firstName, lastName } = req.body;
// 检查用户是否已存在
const existingUser = await User.findOne({
$or: [{ email }, { username }]
});
if (existingUser) {
return res.status(409).json({
success: false,
message: existingUser.email === email ?
'Email already registered' : 'Username already taken'
});
}
// 创建新用户
const user = new User({
username,
email,
password,
firstName,
lastName,
emailVerificationToken: crypto.randomBytes(32).toString('hex')
});
await user.save();
// 发送验证邮件
try {
await emailService.sendVerificationEmail(user.email, user.emailVerificationToken);
} catch (emailError) {
logger.error('Failed to send verification email:', emailError);
// 不阻止注册流程
}
// 生成令牌
const token = user.generateAuthToken();
const refreshToken = user.generateRefreshToken();
// 缓存用户信息
await cacheService.setUser(user._id, user, 3600); // 1小时
res.status(201).json({
success: true,
message: 'User registered successfully',
data: {
user: {
id: user._id,
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
fullName: user.fullName,
role: user.role,
isEmailVerified: user.isEmailVerified
},
tokens: {
accessToken: token,
refreshToken: refreshToken,
expiresIn: process.env.JWT_EXPIRE || '7d'
}
}
});
} catch (error) {
next(error);
}
}
// 用户登录
async login(req, res, next) {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
const { login, password } = req.body; // login可以是email或username
// 查找用户
const user = await User.findOne({
$or: [
{ email: login.toLowerCase() },
{ username: login }
]
}).select('+password');
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// 检查账户是否被锁定
if (user.isLocked) {
return res.status(423).json({
success: false,
message: 'Account temporarily locked due to too many failed login attempts'
});
}
// 验证密码
const isPasswordValid = await user.comparePassword(password);
if (!isPasswordValid) {
await user.incLoginAttempts();
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// 检查账户状态
if (!user.isActive) {
return res.status(403).json({
success: false,
message: 'Account is deactivated'
});
}
// 重置登录尝试次数
if (user.loginAttempts > 0) {
await user.resetLoginAttempts();
}
// 更新最后登录时间
user.lastLogin = new Date();
await user.save();
// 生成令牌
const token = user.generateAuthToken();
const refreshToken = user.generateRefreshToken();
// 缓存用户信息
await cacheService.setUser(user._id, user, 3600);
res.json({
success: true,
message: 'Login successful',
data: {
user: {
id: user._id,
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
fullName: user.fullName,
role: user.role,
isEmailVerified: user.isEmailVerified,
lastLogin: user.lastLogin
},
tokens: {
accessToken: token,
refreshToken: refreshToken,
expiresIn: process.env.JWT_EXPIRE || '7d'
}
}
});
} catch (error) {
next(error);
}
}
// 刷新令牌
async refreshToken(req, res, next) {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({
success: false,
message: 'Refresh token is required'
});
}
// 验证刷新令牌
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
if (decoded.type !== 'refresh') {
return res.status(401).json({
success: false,
message: 'Invalid refresh token'
});
}
// 查找用户
const user = await User.findById(decoded.id);
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'User not found or inactive'
});
}
// 生成新的访问令牌
const newAccessToken = user.generateAuthToken();
res.json({
success: true,
message: 'Token refreshed successfully',
data: {
accessToken: newAccessToken,
expiresIn: process.env.JWT_EXPIRE || '7d'
}
});
} catch (error) {
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: 'Invalid or expired refresh token'
});
}
next(error);
}
}
// 登出
async logout(req, res, next) {
try {
// 从缓存中移除用户信息
await cacheService.deleteUser(req.user.id);
// 可以在这里实现令牌黑名单机制
// await cacheService.blacklistToken(req.token);
res.json({
success: true,
message: 'Logout successful'
});
} catch (error) {
next(error);
}
}
// 邮箱验证
async verifyEmail(req, res, next) {
try {
const { token } = req.params;
const user = await User.findOne({
emailVerificationToken: token
});
if (!user) {
return res.status(400).json({
success: false,
message: 'Invalid or expired verification token'
});
}
user.isEmailVerified = true;
user.emailVerificationToken = undefined;
await user.save();
res.json({
success: true,
message: 'Email verified successfully'
});
} catch (error) {
next(error);
}
}
// 忘记密码
async forgotPassword(req, res, next) {
try {
const { email } = req.body;
const user = await User.findByEmail(email);
if (!user) {
// 为了安全,不透露用户是否存在
return res.json({
success: true,
message: 'If the email exists, a password reset link has been sent'
});
}
// 生成重置令牌
const resetToken = crypto.randomBytes(32).toString('hex');
user.passwordResetToken = crypto.createHash('sha256').update(resetToken).digest('hex');
user.passwordResetExpires = Date.now() + 10 * 60 * 1000; // 10分钟
await user.save();
// 发送重置邮件
try {
await emailService.sendPasswordResetEmail(user.email, resetToken);
} catch (emailError) {
user.passwordResetToken = undefined;
user.passwordResetExpires = undefined;
await user.save();
return res.status(500).json({
success: false,
message: 'Failed to send password reset email'
});
}
res.json({
success: true,
message: 'Password reset email sent'
});
} catch (error) {
next(error);
}
}
// 重置密码
async resetPassword(req, res, next) {
try {
const { token } = req.params;
const { password } = req.body;
// 哈希令牌进行比较
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
const user = await User.findOne({
passwordResetToken: hashedToken,
passwordResetExpires: { $gt: Date.now() }
});
if (!user) {
return res.status(400).json({
success: false,
message: 'Invalid or expired reset token'
});
}
// 更新密码
user.password = password;
user.passwordResetToken = undefined;
user.passwordResetExpires = undefined;
user.loginAttempts = 0;
user.lockUntil = undefined;
await user.save();
res.json({
success: true,
message: 'Password reset successfully'
});
} catch (error) {
next(error);
}
}
}
module.exports = new AuthController();
总结
Node.js后端API开发的核心要点:
🎯 架构设计
- MVC模式:清晰的代码组织结构
- 中间件系统:模块化的请求处理
- 数据模型:完善的数据验证和关系
- 服务层:业务逻辑封装和复用
✅ 安全机制
- JWT认证和授权
- 密码加密和验证
- 速率限制和防护
- 输入验证和过滤
🚀 性能优化
- 数据库索引和查询优化
- 缓存策略和实现
- 响应压缩和优化
- 异步处理和并发
💡 最佳实践
- 错误处理和日志记录
- API文档和测试
- 环境配置和部署
- 监控和维护
掌握Node.js API开发,构建高质量后端服务!
Node.js的异步特性和丰富的生态系统使其成为构建现代化API服务的理想选择,通过合理的架构设计和最佳实践,可以构建出高性能、可维护的后端应用。