标签:Node.js, 微服务, Express, TypeScript, Docker
简介:手把手教学如何使用Express + TypeScript + Docker构建现代化微服务架构,涵盖服务拆分、API设计、中间件使用、容器化部署等关键步骤,提供完整的项目模板和最佳实践指南。
一、引言:为什么选择微服务架构?
随着企业应用规模的不断增长,单体架构(Monolithic Architecture)逐渐暴露出诸多问题:代码耦合度高、开发效率低、部署复杂、难以横向扩展。为了解决这些问题,微服务架构(Microservices Architecture)应运而生。
微服务将一个庞大的应用拆分为多个独立运行的小型服务,每个服务专注于单一业务功能,通过轻量级通信机制(如HTTP/REST、gRPC)进行协作。这种架构具备以下优势:
- 独立部署与发布:每个服务可独立构建、测试、部署。
- 技术异构性支持:不同服务可采用不同编程语言或数据库。
- 弹性伸缩:可根据负载对特定服务进行水平扩展。
- 故障隔离:单个服务崩溃不会导致整个系统瘫痪。
- 团队自治:小团队可独立负责一个服务,提升敏捷性。
在众多后端技术栈中,Node.js 因其非阻塞I/O模型、事件驱动特性以及丰富的生态,在构建高并发、低延迟的服务方面表现出色。结合 TypeScript 提升代码健壮性和可维护性,再通过 Docker 实现环境一致性与快速部署,构成了现代微服务开发的理想组合。
本文将带你从零开始,基于 Express + TypeScript + Docker 搭建一个完整的微服务项目,涵盖服务拆分、接口设计、中间件开发、日志管理、健康检查、容器化部署等核心环节,并附带最佳实践建议。
二、项目结构设计与模块划分
2.1 服务拆分原则
在微服务架构中,服务边界划分是关键。我们遵循以下原则进行拆分:
- 单一职责原则(SRP):每个服务只处理一个业务领域。
- 高内聚、低耦合:服务内部逻辑紧密相关,外部依赖尽量减少。
- 数据所有权:每个服务拥有自己的数据库,避免跨服务直接访问。
- 可独立部署:服务间通过API通信,不共享内存或状态。
以电商系统为例,我们可以拆分为如下服务:
| 服务名称 | 功能描述 |
|---|---|
user-service |
用户注册、登录、权限管理 |
product-service |
商品信息管理、库存查询 |
order-service |
订单创建、支付状态跟踪 |
notification-service |
邮件/短信通知发送 |
gateway-service |
API网关,统一入口,路由转发 |
本例中,我们将重点实现 user-service 和 product-service 两个核心服务,并通过 API 网关统一暴露对外接口。
2.2 项目目录结构(以 user-service 为例)
user-service/
├── src/
│ ├── controllers/
│ │ └── user.controller.ts
│ ├── routes/
│ │ └── user.routes.ts
│ ├── middleware/
│ │ ├── auth.middleware.ts
│ │ └── logger.middleware.ts
│ ├── models/
│ │ └── user.model.ts
│ ├── services/
│ │ └── user.service.ts
│ ├── utils/
│ │ └── validator.ts
│ ├── config/
│ │ └── database.ts
│ ├── app.ts
│ └── server.ts
├── .env
├── tsconfig.json
├── package.json
├── docker-compose.yml
└── Dockerfile
核心目录说明:
controllers/:处理请求响应逻辑,调用 service 层。routes/:定义 RESTful 路由路径及绑定控制器方法。middleware/:自定义中间件,如认证、日志、错误处理。models/:数据模型定义(使用 TypeORM / Prisma)。services/:业务逻辑封装层,与数据库交互。utils/:工具函数,如验证器、加密工具。config/:配置文件,如数据库连接、环境变量。app.ts:Express 应用实例初始化。server.ts:启动入口,监听端口。
三、环境搭建与依赖配置
3.1 初始化项目
mkdir user-service && cd user-service
npm init -y
安装核心依赖:
# 基础依赖
npm install express cors dotenv helmet morgan winston
# TypeScript 相关
npm install --save-dev typescript ts-node @types/node @types/express @types/cors @types/helmet @types/morgan @types/winston
# ORM(推荐使用 TypeORM)
npm install typeorm pg reflect-metadata
# 工具库
npm install uuid bcrypt jsonwebtoken
✅ 推荐使用
ts-node用于开发调试,生产环境编译为 JS 后运行。
3.2 配置 tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
3.3 .env 文件配置
PORT=3001
NODE_ENV=development
DB_HOST=localhost
DB_PORT=5432
DB_NAME=user_db
DB_USER=postgres
DB_PASSWORD=secret
JWT_SECRET=your-super-secret-jwt-key-here
LOG_LEVEL=info
四、Express + TypeScript 项目骨架搭建
4.1 创建 app.ts(应用初始化)
// src/app.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { createConnection } from 'typeorm';
import { logger } from './utils/logger';
import userRoutes from './routes/user.routes';
export const createApp = async () => {
const app = express();
// 安全中间件
app.use(helmet());
app.use(cors());
// 日志中间件
app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
// JSON 解析
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// 路由注册
app.use('/api/v1/users', userRoutes);
// 健康检查端点
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});
// 错误处理中间件(必须放在最后)
app.use((err: any, req: any, res: any, next: any) => {
logger.error('Unhandled error:', err);
res.status(500).json({ error: 'Internal Server Error' });
});
// 连接数据库
try {
await createConnection();
logger.info('Database connected successfully');
} catch (error) {
logger.error('Database connection failed:', error);
process.exit(1);
}
return app;
};
🔍 说明:
- 使用
createConnection()连接 PostgreSQL(示例),也可替换为 MongoDB。morgan输出日志到winston,便于集中管理。/health是 Kubernetes / Docker Compose 健康检查常用路径。
4.2 创建 server.ts(启动入口)
// src/server.ts
import { createApp } from './app';
import { PORT } from './config/environment';
async function startServer() {
const app = await createApp();
app.listen(PORT, () => {
console.log(`🚀 User Service is running on port ${PORT}`);
});
}
startServer().catch(err => {
console.error('Failed to start server:', err);
});
⚠️
environment.ts示例:
// src/config/environment.ts
import dotenv from 'dotenv';
dotenv.config();
export const PORT = parseInt(process.env.PORT || '3001', 10);
export const NODE_ENV = process.env.NODE_ENV || 'development';
export const JWT_SECRET = process.env.JWT_SECRET || 'default-secret';
五、用户服务完整实现
5.1 数据模型定义(TypeORM)
// src/models/user.model.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 100 })
name: string;
@Column({ unique: true, length: 100 })
email: string;
@Column({ length: 100 })
password: string;
@Column({ default: false })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
📌 说明:使用
uuid作为主键,提高安全性;启用软删除可添加@DeleteDateColumn()。
5.2 服务层(业务逻辑)
// src/services/user.service.ts
import { getRepository } from 'typeorm';
import { User } from '../models/user.model';
import { hashPassword } from '../utils/crypto';
export class UserService {
private userRepository = getRepository(User);
async createUser(name: string, email: string, password: string): Promise<User> {
const hashedPassword = await hashPassword(password);
const user = this.userRepository.create({
name,
email,
password: hashedPassword,
isActive: true,
});
return await this.userRepository.save(user);
}
async findUserByEmail(email: string): Promise<User | null> {
return await this.userRepository.findOne({ where: { email } });
}
async getUserById(id: string): Promise<User | null> {
return await this.userRepository.findOne({ where: { id } });
}
}
🔐 加密工具函数(
crypto.ts):
// src/utils/crypto.ts
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 10;
export const hashPassword = async (password: string): Promise<string> => {
return await bcrypt.hash(password, SALT_ROUNDS);
};
export const comparePassword = async (
plainPassword: string,
hashedPassword: string
): Promise<boolean> => {
return await bcrypt.compare(plainPassword, hashedPassword);
};
5.3 控制器层(处理请求)
// src/controllers/user.controller.ts
import { Request, Response } from 'express';
import { UserService } from '../services/user.service';
import { validateEmail, validateName } from '../utils/validator';
const userService = new UserService();
export const createUser = async (req: Request, res: Response) => {
try {
const { name, email, password } = req.body;
if (!validateName(name)) {
return res.status(400).json({ error: 'Invalid name' });
}
if (!validateEmail(email)) {
return res.status(400).json({ error: 'Invalid email' });
}
if (password.length < 6) {
return res.status(400).json({ error: 'Password too short' });
}
const existingUser = await userService.findUserByEmail(email);
if (existingUser) {
return res.status(409).json({ error: 'User already exists' });
}
const user = await userService.createUser(name, email, password);
res.status(201).json({ message: 'User created successfully', user: { id: user.id, name: user.name, email: user.email } });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
};
export const getUserById = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const user = await userService.getUserById(id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ user });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
};
5.4 路由定义
// src/routes/user.routes.ts
import { Router } from 'express';
import { createUser, getUserById } from '../controllers/user.controller';
const router = Router();
router.post('/', createUser);
router.get('/:id', getUserById);
export default router;
六、中间件开发与最佳实践
6.1 自定义认证中间件
// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { JWT_SECRET } from '../config/environment';
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Access token required' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
(req as any).userId = decoded.id;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
✅ 使用方式:在受保护路由前加入中间件
router.get('/profile', authenticate, getUserProfile);
6.2 全局错误处理中间件
// src/middleware/error.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';
export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
logger.error('Error caught:', err);
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}
if (err.status) {
return res.status(err.status).json({ error: err.message });
}
res.status(500).json({ error: 'Internal Server Error' });
};
📌 推荐集成
express-validator用于表单校验。
6.3 日志管理(Winston)
// src/utils/logger.ts
import { createLogger, format, transports } from 'winston';
const { combine, timestamp, printf } = format;
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} [${level.toUpperCase()}] ${message}`;
});
export const logger = createLogger({
level: process.env.LOG_LEVEL || 'info',
format: combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
logFormat
),
transports: [
new transports.Console(),
new transports.File({ filename: 'logs/application.log' }),
],
});
📁 日志输出路径:
logs/,建议配合logrotate或 ELK 收集分析。
七、容器化部署:Docker + Docker Compose
7.1 编写 Dockerfile
# Dockerfile
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3001
CMD ["node", "dist/server.js"]
✅
npm ci保证依赖版本一致,适合 CI/CD 流水线。
7.2 构建与运行镜像
# 构建镜像
docker build -t user-service:v1 .
# 运行容器
docker run -p 3001:3001 --env-file .env -d user-service:v1
7.3 docker-compose.yml 集成多服务
# docker-compose.yml
version: '3.8'
services:
db:
image: postgres:15
container_name: user-db
environment:
POSTGRES_DB: user_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- microservices-net
user-service:
build:
context: .
dockerfile: Dockerfile
container_name: user-service
ports:
- "3001:3001"
depends_on:
- db
environment:
- NODE_ENV=production
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=user_db
- DB_USER=postgres
- DB_PASSWORD=secret
- JWT_SECRET=supersecretkey
networks:
- microservices-net
restart: unless-stopped
gateway-service:
build:
context: ./gateway-service
dockerfile: Dockerfile
container_name: gateway-service
ports:
- "8080:8080"
depends_on:
- user-service
- product-service
networks:
- microservices-net
restart: unless-stopped
networks:
microservices-net:
driver: bridge
volumes:
postgres_data:
💡 说明:
- 所有服务共享同一个网络
microservices-net,可使用服务名互访。depends_on确保数据库先启动。- 使用
restart: unless-stopped保证服务异常重启。
八、性能优化与监控建议
8.1 启用 HTTP/2(可选)
// 改造服务器使用 HTTPS + HTTP/2
import http2 from 'http2';
import fs from 'fs';
const server = http2.createSecureServer({
key: fs.readFileSync('cert/key.pem'),
cert: fs.readFileSync('cert/cert.pem'),
}, (req, res) => {
// 处理逻辑
});
✅ 适用于高并发场景,但需证书支持。
8.2 引入 Prometheus + Grafana 监控
安装 prom-client:
npm install prom-client
注册指标:
// src/metrics.ts
import { Registry } from 'prom-client';
const register = new Registry();
register.setDefaultLabels({ app: 'user-service' });
const httpRequestDuration = new register.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
});
export { register, httpRequestDuration };
在中间件中记录:
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration.observe(
{ method: req.method, route: req.route?.path || req.path, status_code: res.statusCode },
duration
);
});
next();
});
📊 Prometheus 可采集
/metrics端点,Grafana 可可视化图表。
九、总结与最佳实践清单
✅ 本项目核心亮点总结:
| 特性 | 实现方式 |
|---|---|
| 类型安全 | TypeScript + 接口定义 |
| 高可维护性 | 分层架构(控制器/服务/模型) |
| 可靠部署 | Docker 容器化 |
| 环境隔离 | .env + docker-compose |
| 安全性 | JWT 认证 + 密码哈希 |
| 可观测性 | Winston 日志 + Prometheus 指标 |
| 易于扩展 | 服务拆分 + API 网关 |
🏆 最佳实践清单(推荐遵循)
- 始终使用
TypeScript:提升类型安全,减少运行时错误。 - 服务间通信使用 REST/gRPC:避免直接数据库共享。
- 所有敏感信息使用
.env:禁止硬编码密码、密钥。 - 使用
Docker Compose管理多服务:简化本地开发与测试。 - 引入健康检查端点
/health:供 K8s / Docker Swarm 使用。 - 使用
winston或pino:结构化日志,便于分析。 - 定期更新依赖:使用
npm audit和renovate工具。 - 实施 CI/CD 流水线:自动构建、测试、部署。
- 使用
JWT+refresh token:增强身份验证安全性。 - 监控与告警:集成 Prometheus + AlertManager。
十、结语
通过本教程,你已掌握使用 Express + TypeScript + Docker 构建高性能、可维护、可扩展的微服务系统的完整流程。从项目初始化、分层设计、数据库集成,到容器化部署与可观测性建设,每一步都遵循现代工程最佳实践。
未来你可以在此基础上进一步拓展:
- 添加消息队列(RabbitMQ/Kafka)实现异步任务处理;
- 引入 Redis 缓存热点数据;
- 使用 Kubernetes 进行集群管理;
- 集成 OpenTelemetry 做分布式追踪。
微服务不是“银弹”,但它是一种应对复杂系统演进的成熟范式。只要坚持单一职责、松耦合、自动化、可观测,就能打造真正可靠的云原生应用。
🚀 现在就动手搭建你的第一个微服务吧!
📝 附录:完整项目模板仓库参考
- GitHub 地址:https://github.com/example/node-microservice-template
- 包含:完整源码、Dockerfile、docker-compose.yml、CI/CD 配置
- 支持一键部署至 AWS / GCP / VPS
✅ 文章结束

评论 (0)