Node.js + Express + MongoDB 微服务架构实战:从零搭建企业级后端服务

GoodBird
GoodBird 2026-02-11T19:14:06+08:00
0 0 0

一、引言:为什么选择 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 认证流程概览

  1. 用户注册 → 存储加密密码
  2. 用户登录 → 验证凭据 → 返回 JWT Token
  3. 请求携带 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 部署方案

可进一步演进的方向:

  1. 服务发现与注册:集成 Consul / Eureka
  2. API 网关:使用 Kong / Traefik 统一入口
  3. 消息队列:引入 RabbitMQ / Kafka 处理异步任务
  4. 分布式追踪:集成 OpenTelemetry
  5. CI/CD 流水线:GitHub Actions / Jenkins
  6. 可观测性: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)

    0/2000