一、引言:为什么选择 Node.js + Express + MongoDB 构建微服务?
在现代软件开发中,微服务架构已成为构建可扩展、高可用、易维护的企业级后端系统的主流范式。随着业务复杂度的上升,传统的单体应用逐渐暴露出部署困难、技术栈僵化、团队协作低效等问题。而微服务通过将系统拆分为多个独立的服务单元,实现了模块解耦、独立部署与弹性伸缩。
在众多技术选型中,Node.js + Express + MongoDB 组合因其轻量、高效、异步非阻塞特性,成为构建高性能微服务的理想选择。尤其适用于数据密集型、实时交互频繁的场景(如社交平台、电商平台、IoT 后台等)。
技术栈优势解析:
- Node.js:基于 V8 引擎,支持事件驱动和非阻塞 I/O,适合高并发请求处理。
- Express:极简、灵活的 Web 框架,提供强大的路由、中间件机制,是构建 RESTful API 的首选。
- MongoDB:文档型数据库,天然支持嵌套结构,易于与 JSON 兼容的数据模型对接,具备良好的水平扩展能力。
本篇文章将带你从零开始,手把手搭建一个完整的企业级微服务架构,涵盖项目初始化、模块设计、安全认证、数据验证、日志监控、错误处理、部署建议等关键环节,确保代码质量与生产可用性。
二、项目初始化与目录结构设计
2.1 初始化项目
mkdir user-service
cd user-service
npm init -y
安装核心依赖:
npm install express mongoose dotenv cors helmet morgan jsonwebtoken bcryptjs Joi winston @types/express @types/node --save
npm install typescript ts-node @types/cors @types/helmet @types/morgan @types/jsonwebtoken @types/bcryptjs @types/joi @types/winston --save-dev
💡 提示:使用
--save安装运行时依赖,--save-dev安装开发时依赖。
2.2 配置 TypeScript
创建 tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
2.3 推荐的项目目录结构
user-service/
├── src/
│ ├── config/ # 环境配置、数据库连接
│ ├── controllers/ # 业务逻辑控制器
│ ├── routes/ # 路由定义
│ ├── middleware/ # 中间件(认证、日志、验证等)
│ ├── models/ # Mongoose 模型定义
│ ├── utils/ # 工具函数(加密、邮件发送等)
│ ├── interfaces/ # TypeScript 接口定义
│ ├── services/ # 业务服务层(可选分层)
│ └── app.ts # 应用入口文件
├── .env # 环境变量
├── .env.example # 示例环境变量
├── package.json
├── tsconfig.json
└── README.md
✅ 最佳实践:采用“分层架构”(Layered Architecture),将关注点分离,便于维护和测试。
三、环境配置与数据库连接
3.1 环境变量管理
创建 .env 文件:
PORT=3000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/userdb
JWT_SECRET=your-super-secret-jwt-key-here
JWT_EXPIRES_IN=7d
LOG_LEVEL=info
创建 .env.example 作为模板:
PORT=3000
NODE_ENV=development|production
MONGODB_URI=mongodb://<username>:<password>@<host>:<port>/<dbname>
JWT_SECRET=your-jwt-secret-key
JWT_EXPIRES_IN=7d
LOG_LEVEL=debug|info|warn|error
3.2 连接 MongoDB
src/config/database.ts
import mongoose from 'mongoose';
import { config } from 'dotenv';
config();
const connectDB = async (): Promise<void> => {
try {
await mongoose.connect(process.env.MONGODB_URI!);
console.log('✅ MongoDB connected successfully');
} catch (error) {
console.error('❌ MongoDB connection error:', error);
process.exit(1);
}
};
export default connectDB;
⚠️ 注意:生产环境中应使用连接池、启用 SSL、设置超时重试策略。
3.3 增强连接配置(可选)
// src/config/database.ts (增强版)
const connectDB = async (): Promise<void> => {
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
retryWrites: true,
w: 'majority',
j: true,
};
try {
await mongoose.connect(process.env.MONGODB_URI!, options);
console.log('✅ MongoDB connected with options');
// 监听连接状态
mongoose.connection.on('connected', () => {
console.log('📡 MongoDB connected');
});
mongoose.connection.on('disconnected', () => {
console.warn('⚠️ MongoDB disconnected');
});
mongoose.connection.on('error', (err) => {
console.error('❌ MongoDB error:', err);
});
} catch (error) {
console.error('❌ Failed to connect to MongoDB:', error);
process.exit(1);
}
};
四、RESTful API 设计规范与路由实现
4.1 RESTful API 基本原则
遵循以下最佳实践:
| 动作 | HTTP 方法 | 路径 | 描述 |
|---|---|---|---|
| 获取用户列表 | GET | /api/users |
列表查询 |
| 获取单个用户 | GET | /api/users/:id |
根据 ID 查询 |
| 创建用户 | POST | /api/users |
新增用户 |
| 更新用户 | PUT/PATCH | /api/users/:id |
全量/部分更新 |
| 删除用户 | DELETE | /api/users/:id |
删除用户 |
4.2 路由定义示例
src/routes/userRoutes.ts
import { Router } from 'express';
import * as userController from '../controllers/userController';
import { authMiddleware } from '../middleware/authMiddleware';
import { validateUser } from '../middleware/validationMiddleware';
const router = Router();
// Public routes
router.post('/register', validateUser, userController.registerUser);
router.post('/login', userController.loginUser);
// Protected routes
router.get('/profile', authMiddleware, userController.getProfile);
router.get('/', authMiddleware, userController.getAllUsers);
router.get('/:id', authMiddleware, userController.getUserById);
router.put('/:id', authMiddleware, validateUser, userController.updateUser);
router.delete('/:id', authMiddleware, userController.deleteUser);
export default router;
4.3 应用主入口整合路由
src/app.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import connectDB from './config/database';
import userRoutes from './routes/userRoutes';
import { errorHandler } from './middleware/errorHandler';
const app = express();
// Middleware
app.use(helmet()); // 安全头
app.use(cors()); // 允许跨域
app.use(morgan('dev')); // 日志
app.use(express.json({ limit: '10mb' })); // 支持大请求体
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/users', userRoutes);
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});
// Global error handler
app.use(errorHandler);
// Start server
const PORT = process.env.PORT || 3000;
const startServer = async () => {
await connectDB();
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});
};
export { app, startServer };
五、用户模型与数据验证
5.1 用户模型定义(Mongoose Schema)
src/models/User.ts
import mongoose, { Document, Schema } from 'mongoose';
import bcrypt from 'bcryptjs';
export interface IUser extends Document {
username: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
const userSchema = new Schema<IUser>({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3,
maxlength: 30,
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/, 'Please enter a valid email'],
},
password: {
type: String,
required: true,
minlength: 6,
select: false, // 保证密码不被查询返回
},
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Pre-save hook: 密码加密
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// Instance method: 密码校验
userSchema.methods.comparePassword = async function (candidatePassword: string): Promise<boolean> {
return await bcrypt.compare(candidatePassword, this.password);
};
export default mongoose.model<IUser>('User', userSchema);
✅ 关键点:
- 使用
select: false防止密码泄露- 使用
pre('save')加密密码timestamps: true自动添加createdAt/updatedAt
5.2 数据验证(Joi + 自定义中间件)
src/middleware/validationMiddleware.ts
import Joi from 'joi';
// User validation schema
const userSchema = Joi.object({
username: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
export const validateUser = (req, res, next) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: error.details.map(detail => detail.message),
});
}
next();
};
📌 说明:
Joi是一个强大且流行的验证库,支持嵌套结构、自定义规则、错误信息定制。
六、身份认证与授权(JWT)
6.1 JWT 认证流程概览
- 用户注册 → 存储加密密码
- 用户登录 → 验证凭据 → 返回 JWT Token
- 请求携带 Token → 中间件解析并验证 → 放行或拒绝
6.2 登录与注册控制器
src/controllers/userController.ts
import { Request, Response } from 'express';
import User from '../models/User';
import jwt from 'jsonwebtoken';
import { generateToken } from '../utils/tokenUtils';
import { hashPassword } from '../utils/cryptoUtils';
export const registerUser = async (req: Request, res: Response) => {
try {
const { username, email, password } = req.body;
// 检查用户是否存在
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({
success: false,
message: 'Email already in use'
});
}
const newUser = new User({ username, email, password });
await newUser.save();
const token = generateToken(newUser._id.toString());
res.status(201).json({
success: true,
message: 'User registered successfully',
data: {
id: newUser._id,
username: newUser.username,
email: newUser.email,
token
}
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
export const loginUser = async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
const token = generateToken(user._id.toString());
res.json({
success: true,
message: 'Login successful',
data: {
id: user._id,
username: user.username,
email: user.email,
token
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
export const getProfile = async (req: Request, res: Response) => {
try {
const userId = (req as any).user.id; // 由 authMiddleware 注入
const user = await User.findById(userId).select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
export const getAllUsers = async (req: Request, res: Response) => {
try {
const users = await User.find().select('-password');
res.json({
success: true,
data: users
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
export const getUserById = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const user = await User.findById(id).select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
export const updateUser = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const updates = req.body;
const user = await User.findByIdAndUpdate(id, updates, {
new: true,
runValidators: true
}).select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.json({
success: true,
message: 'User updated successfully',
data: user
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
export const deleteUser = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const user = await User.findByIdAndDelete(id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.json({
success: true,
message: 'User deleted successfully'
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
};
6.3 JWT 工具函数
src/utils/tokenUtils.ts
import jwt from 'jsonwebtoken';
export const generateToken = (userId: string): string => {
const secret = process.env.JWT_SECRET!;
const expiresIn = process.env.JWT_EXPIRES_IN || '7d';
return jwt.sign({ id: userId }, secret, { expiresIn });
};
export const verifyToken = (token: string): any => {
const secret = process.env.JWT_SECRET!;
try {
return jwt.verify(token, secret);
} catch (error) {
throw new Error('Invalid or expired token');
}
};
6.4 认证中间件
src/middleware/authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { verifyToken } from '../utils/tokenUtils';
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: 'Access denied. No token provided.'
});
}
const token = authHeader.substring(7); // Remove 'Bearer '
try {
const decoded = verifyToken(token);
(req as any).user = decoded; // Inject user into request
next();
} catch (error) {
return res.status(403).json({
success: false,
message: 'Invalid or expired token'
});
}
};
七、错误处理与日志记录
7.1 全局错误处理器
src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
logger.error(err.stack);
res.status(err.status || 500).json({
success: false,
message: err.message || 'Something went wrong',
...(process.env.NODE_ENV === 'development' ? { stack: err.stack } : {})
});
};
✅ 生产环境隐藏
stack,防止信息泄露。
7.2 结合 Winston 实现结构化日志
// 例如在 controller 内部
logger.info('User login attempt', { email: req.body.email, ip: req.ip });
八、安全加固建议
| 安全项 | 建议 |
|---|---|
| 密码加密 | 使用 bcryptjs 并设置 saltRounds = 10 |
| JWT 有效期 | 限制为 7 天以内,避免长期有效 |
| HTTPS | 生产环境必须启用,禁止明文传输 |
| CORS | 仅允许可信域名访问 |
| XSS 防护 | 使用 helmet + sanitize-html |
| SQL/NoSQL 注入 | 使用参数化查询或 ORM(如 Mongoose)自动防护 |
| Rate Limiting | 引入 express-rate-limit 防止暴力破解 |
示例:限流中间件
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});
app.use('/api/users/login', limiter);
九、部署与运维建议
9.1 Docker 化部署
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production
COPY . .
EXPOSE 3000
CMD ["node", "dist/app.js"]
docker-compose.yml
version: '3.8'
services:
mongodb:
image: mongo:6
container_name: mongo-userdb
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
restart: unless-stopped
userservice:
build: .
container_name: user-service
ports:
- "3000:3000"
depends_on:
- mongodb
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongodb:27017/userdb
- JWT_SECRET=supersecretkey
restart: unless-stopped
volumes:
mongo-data:
9.2 PM2 进程管理(生产推荐)
npm install pm2 -g
pm2 start dist/app.js --name "user-service"
pm2 startup
pm2 save
十、总结与未来演进方向
本文完整演示了如何使用 Node.js + Express + MongoDB 搭建一个符合企业级标准的微服务架构。我们覆盖了以下核心能力:
- ✅ 分层架构设计
- ✅ RESTful API 规范化
- ✅ 完整的身份认证(JWT)
- ✅ 数据验证与安全防护
- ✅ 日志与错误处理
- ✅ Docker + PM2 部署方案
可进一步演进的方向:
- 服务发现与注册:集成 Consul / Eureka
- API 网关:使用 Kong / Traefik 统一入口
- 消息队列:引入 RabbitMQ / Kafka 处理异步任务
- 分布式追踪:集成 OpenTelemetry
- CI/CD 流水线:GitHub Actions / Jenkins
- 可观测性:Prometheus + Grafana 监控指标
附录:完整项目结构参考
user-service/
├── src/
│ ├── config/
│ │ └── database.ts
│ ├── controllers/
│ │ └── userController.ts
│ ├── routes/
│ │ └── userRoutes.ts
│ ├── middleware/
│ │ ├── authMiddleware.ts
│ │ ├── validationMiddleware.ts
│ │ └── errorHandler.ts
│ ├── models/
│ │ └── User.ts
│ ├── utils/
│ │ ├── tokenUtils.ts
│ │ └── cryptoUtils.ts
│ ├── app.ts
│ └── index.ts
├── .env
├── .env.example
├── Dockerfile
├── docker-compose.yml
├── tsconfig.json
├── package.json
└── README.md
🚀 结语:微服务不是银弹,但它是应对复杂系统演进的必要手段。掌握这套组合技,你已具备构建现代化后端系统的坚实基础。持续学习、迭代优化,才是通往高级工程师的必经之路。
标签:Node.js, Express, MongoDB, 微服务, API设计

评论 (0)