express-microservices-architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExpress.js Microservices Architecture
Express.js 微服务架构
A comprehensive skill for building production-ready microservices with Express.js. Master middleware patterns, routing strategies, error handling, scalability techniques, and deployment architectures for Node.js microservices at scale.
这是一份使用Express.js构建生产级微服务的全面指南。掌握Node.js微服务的中间件模式、路由策略、错误处理、可扩展性技术以及部署架构。
When to Use This Skill
何时使用本技能
Use this skill when:
- Building RESTful APIs and microservices with Node.js
- Designing scalable distributed systems with Express.js
- Implementing middleware-based architecture patterns
- Creating API gateways and service mesh architectures
- Developing production-ready Node.js applications
- Migrating monoliths to microservices architecture
- Building event-driven microservices
- Implementing authentication, authorization, and security layers
- Optimizing Express.js applications for high performance
- Setting up monitoring, logging, and observability
- Deploying Express.js apps with Docker and Kubernetes
- Implementing circuit breakers and resilience patterns
在以下场景使用本技能:
- 使用Node.js构建RESTful API和微服务
- 使用Express.js设计可扩展的分布式系统
- 实现基于中间件的架构模式
- 创建API网关和服务网格架构
- 开发生产级Node.js应用
- 将单体应用迁移到微服务架构
- 构建事件驱动的微服务
- 实现认证、授权和安全层
- 优化Express.js应用以实现高性能
- 搭建监控、日志和可观测性系统
- 使用Docker和Kubernetes部署Express.js应用
- 实现断路器和弹性模式
Core Concepts
核心概念
Express.js Fundamentals
Express.js 基础
Express.js is a minimal and flexible Node.js web application framework that provides robust features for web and mobile applications. It's the de facto standard for building Node.js APIs and microservices.
Key Characteristics:
- Minimal: Unopinionated framework with essential web app features
- Middleware-based: Request/response pipeline architecture
- Routing: Powerful routing mechanism with parameter support
- Template Engines: Support for various view engines
- Performance: Built on top of Node.js for high performance
- Extensible: Rich ecosystem of middleware and plugins
Express.js是一个轻量灵活的Node.js Web应用框架,为Web和移动应用提供强大功能。它是构建Node.js API和微服务的事实标准。
关键特性:
- 轻量:仅包含Web应用核心功能的无主见框架
- 基于中间件:请求/响应管道架构
- 路由:支持参数的强大路由机制
- 模板引擎:支持多种视图引擎
- 高性能:基于Node.js构建,性能优异
- 可扩展:拥有丰富的中间件和插件生态
Middleware Architecture
中间件架构
Middleware functions are the backbone of Express.js applications. They have access to the request object (), response object (), and the next middleware function ().
reqresnextMiddleware Flow:
Request → Middleware 1 → Middleware 2 → ... → Route Handler → Response
↓ ↓ ↓
Error Handler Error Handler Error HandlerMiddleware Types:
- Application-level middleware: Bound to instance
app - Router-level middleware: Bound to instance
express.Router() - Error-handling middleware: Has 4 parameters (err, req, res, next)
- Built-in middleware: Express built-in functions (static, json, urlencoded)
- Third-party middleware: External packages (cors, helmet, morgan)
中间件函数是Express.js应用的核心。它们可以访问请求对象()、响应对象()以及下一个中间件函数()。
reqresnext中间件流程:
请求 → 中间件1 → 中间件2 → ... → 路由处理器 → 响应
↓ ↓ ↓
错误处理器 错误处理器 错误处理器中间件类型:
- 应用级中间件:绑定到实例
app - 路由级中间件:绑定到实例
express.Router() - 错误处理中间件:包含4个参数(err, req, res, next)
- 内置中间件:Express内置函数(static、json、urlencoded)
- 第三方中间件:外部包(cors、helmet、morgan)
Routing Strategies
路由策略
Express routing enables you to map HTTP methods and URLs to handler functions.
Routing Components:
- Route paths: String patterns, regex, or path parameters
- Route parameters: Named URL segments (:userId)
- Route handlers: Single or multiple callback functions
- Response methods: res.send(), res.json(), res.status(), etc.
- Router instances: Modular, mountable route handlers
Express路由允许你将HTTP方法和URL映射到处理器函数。
路由组件:
- 路由路径:字符串模式、正则表达式或路径参数
- 路由参数:命名URL段(:userId)
- 路由处理器:单个或多个回调函数
- 响应方法:res.send()、res.json()、res.status()等
- 路由实例:模块化、可挂载的路由处理器
Error Handling
错误处理
Error handling in Express requires special middleware with 4 parameters: .
(err, req, res, next)Error Handling Flow:
- Synchronous errors are caught automatically
- Asynchronous errors must be passed to
next(err) - Error middleware processes errors centrally
- Proper status codes and error formats returned
Express中的错误处理需要使用包含4个参数的特殊中间件:。
(err, req, res, next)错误处理流程:
- 同步错误会被自动捕获
- 异步错误必须传递给
next(err) - 错误中间件集中处理错误
- 返回正确的状态码和错误格式
Microservices Principles
微服务原则
Characteristics of Microservices:
- Single Responsibility: Each service does one thing well
- Independence: Services can be deployed independently
- Decentralized: Each service owns its data
- Resilience: Failure in one service doesn't crash entire system
- Scalability: Scale services independently based on demand
- Technology Diversity: Different services can use different tech stacks
微服务特性:
- 单一职责:每个服务专注于一件事并做好
- 独立性:服务可独立部署
- 去中心化:每个服务拥有自己的数据
- 弹性:单个服务故障不会导致整个系统崩溃
- 可扩展性:可根据需求独立扩展服务
- 技术多样性:不同服务可使用不同技术栈
Microservices Patterns
微服务模式
Pattern 1: API Gateway Pattern
模式1:API网关模式
The API Gateway acts as a single entry point for all client requests, routing them to appropriate microservices.
Benefits:
- Single entry point for clients
- Request routing and composition
- Authentication and authorization
- Rate limiting and throttling
- Request/response transformation
- Protocol translation
Implementation Structure:
Client → API Gateway → Microservice 1 (Users)
→ Microservice 2 (Orders)
→ Microservice 3 (Products)
→ Microservice 4 (Notifications)API网关作为所有客户端请求的单一入口点,将请求路由到对应的微服务。
优势:
- 为客户端提供单一入口点
- 请求路由和组合
- 认证和授权
- 速率限制和流量控制
- 请求/响应转换
- 协议转换
实现结构:
客户端 → API网关 → 微服务1(用户)
→ 微服务2(订单)
→ 微服务3(商品)
→ 微服务4(通知)Pattern 2: Service Discovery
模式2:服务发现
Services register themselves and discover other services dynamically.
Approaches:
- Client-side discovery: Client queries service registry
- Server-side discovery: Load balancer queries registry
- DNS-based discovery: Using DNS for service location
Popular Tools:
- Consul
- Eureka
- etcd
- Kubernetes built-in discovery
服务自动注册并动态发现其他服务。
实现方式:
- 客户端发现:客户端查询服务注册中心
- 服务端发现:负载均衡器查询注册中心
- 基于DNS的发现:使用DNS定位服务
热门工具:
- Consul
- Eureka
- etcd
- Kubernetes内置发现机制
Pattern 3: Circuit Breaker
模式3:断路器
Prevents cascading failures by stopping requests to failing services.
States:
- Closed: Normal operation, requests pass through
- Open: Service failing, requests fail immediately
- Half-Open: Testing if service recovered
通过停止向故障服务发送请求,防止级联故障。
状态:
- 关闭:正常运行,请求正常通过
- 打开:服务故障,请求立即失败
- 半开:测试服务是否已恢复
Pattern 4: Event-Driven Architecture
模式4:事件驱动架构
Services communicate through events instead of direct calls.
Components:
- Event producers: Services that emit events
- Event consumers: Services that listen to events
- Message broker: RabbitMQ, Kafka, Redis
- Event store: Persist events for replay
服务通过事件而非直接调用进行通信。
组件:
- 事件生产者:发送事件的服务
- 事件消费者:监听事件的服务
- 消息代理:RabbitMQ、Kafka、Redis
- 事件存储:持久化事件以便重放
Pattern 5: Database per Service
模式5:服务专属数据库
Each microservice owns its database, ensuring loose coupling.
Benefits:
- Service independence
- Technology diversity
- Easier scaling
- Clear boundaries
Challenges:
- Distributed transactions
- Data consistency
- Joins across services
每个微服务拥有自己的数据库,确保松耦合。
优势:
- 服务独立性
- 技术多样性
- 更易扩展
- 清晰的边界
挑战:
- 分布式事务
- 数据一致性
- 跨服务关联查询
Pattern 6: Saga Pattern
模式6:Saga模式
Manages distributed transactions across multiple services.
Types:
- Choreography: Services coordinate through events
- Orchestration: Central coordinator manages transaction
管理跨多个服务的分布式事务。
类型:
- 编排式:服务通过事件协调
- ** choreography**:中央协调器管理事务
Pattern 7: CQRS (Command Query Responsibility Segregation)
模式7:CQRS(命令查询职责分离)
Separate read and write operations into different models.
Benefits:
- Optimized read/write models
- Scalability
- Performance
- Flexibility
将读操作和写操作分离为不同的模型。
优势:
- 优化的读/写模型
- 可扩展性
- 高性能
- 灵活性
Middleware Architecture Patterns
中间件架构模式
Custom Middleware Development
自定义中间件开发
Middleware functions execute in the order they're defined.
Basic Middleware Structure:
javascript
const express = require('express');
const app = express();
// Basic middleware
const requestLogger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // Pass control to next middleware
};
app.use(requestLogger);From Context7 - Saving Data in Request Object:
javascript
const express = require('express');
const app = express();
const port = 3000;
// Middleware to add user data to the request object
const addUserInfo = (req, res, next) => {
req.user = {
id: 123,
username: 'testuser'
};
next();
};
// Middleware to add request timestamp
const addTimestamp = (req, res, next) => {
req.requestTime = Date.now();
next();
};
// Apply middleware globally
app.use(addUserInfo);
app.use(addTimestamp);
app.get('/', (req, res) => {
const userId = req.user.id;
const username = req.user.username;
const timestamp = req.requestTime;
res.send(`User ID: ${userId}, Username: ${username}, Request Time: ${new Date(timestamp).toISOString()}`);
});
app.listen(port, () => {
console.log(`Request data sharing example listening at http://localhost:${port}`);
});中间件函数按定义顺序执行。
基础中间件结构:
javascript
const express = require('express');
const app = express();
// Basic middleware
const requestLogger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // Pass control to next middleware
};
app.use(requestLogger);来自Context7 - 在请求对象中保存数据:
javascript
const express = require('express');
const app = express();
const port = 3000;
// Middleware to add user data to the request object
const addUserInfo = (req, res, next) => {
req.user = {
id: 123,
username: 'testuser'
};
next();
};
// Middleware to add request timestamp
const addTimestamp = (req, res, next) => {
req.requestTime = Date.now();
next();
};
// Apply middleware globally
app.use(addUserInfo);
app.use(addTimestamp);
app.get('/', (req, res) => {
const userId = req.user.id;
const username = req.user.username;
const timestamp = req.requestTime;
res.send(`User ID: ${userId}, Username: ${username}, Request Time: ${new Date(timestamp).toISOString()}`);
});
app.listen(port, () => {
console.log(`Request data sharing example listening at http://localhost:${port}`);
});Error-Handling Middleware
错误处理中间件
Error middleware has 4 parameters and should be defined after all other middleware.
From Context7 - Error Handling Middleware:
javascript
const express = require('express');
const app = express();
const port = 3000;
// A regular middleware
app.use((req, res, next) => {
console.log('Request received');
next(); // Pass control to the next middleware
});
// A route that might throw an error
app.get('/throw-error', (req, res, next) => {
// Simulate an error
const error = new Error('This is a simulated error');
error.status = 400;
next(error);
});
// Error-handling middleware (must have 4 arguments)
app.use((err, req, res, next) => {
console.error('Error caught:', err.message);
res.status(err.status || 500).send(`An error occurred: ${err.message}`);
});
app.listen(port, () => {
console.log(`Error middleware example listening at http://localhost:${port}`);
});From Context7 - Global Error Handler:
javascript
app.use(express.bodyParser())
app.use(express.cookieParser())
app.use(express.session())
app.use(app.router) // the router itself (app.get(), app.put() etc)
app.use(function(err, req, res, next){
// if an error occurs Connect will pass it down
// through these "error-handling" middleware
// allowing you to respond however you like
res.send(500, { error: 'Sorry something bad happened!' });
})错误中间件包含4个参数,应定义在所有其他中间件之后。
来自Context7 - 错误处理中间件:
javascript
const express = require('express');
const app = express();
const port = 3000;
// A regular middleware
app.use((req, res, next) => {
console.log('Request received');
next(); // Pass control to the next middleware
});
// A route that might throw an error
app.get('/throw-error', (req, res, next) => {
// Simulate an error
const error = new Error('This is a simulated error');
error.status = 400;
next(error);
});
// Error-handling middleware (must have 4 arguments)
app.use((err, req, res, next) => {
console.error('Error caught:', err.message);
res.status(err.status || 500).send(`An error occurred: ${err.message}`);
});
app.listen(port, () => {
console.log(`Error middleware example listening at http://localhost:${port}`);
});来自Context7 - 全局错误处理器:
javascript
app.use(express.bodyParser())
app.use(express.cookieParser())
app.use(express.session())
app.use(app.router) // the router itself (app.get(), app.put() etc)
app.use(function(err, req, res, next){
// if an error occurs Connect will pass it down
// through these "error-handling" middleware
// allowing you to respond however you like
res.send(500, { error: 'Sorry something bad happened!' });
})Route-Specific Middleware
路由专属中间件
Apply middleware to specific routes for targeted functionality.
From Context7 - Route Middleware:
javascript
const express = require('express');
const app = express();
const port = 3000;
// Middleware function
const requestLogger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
};
// Apply middleware to a specific route
app.get('/protected', requestLogger, (req, res) => {
res.send('This route is protected by middleware!');
});
// Apply middleware to multiple routes
const adminMiddleware = (req, res, next) => {
console.log('Admin access check...');
// In a real app, you'd check user roles here
next();
};
app.get('/admin/dashboard', adminMiddleware, (req, res) => {
res.send('Welcome to the admin dashboard!');
});
// Middleware applied globally
app.use(requestLogger);
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Route middleware example listening at http://localhost:${port}`);
});将中间件应用于特定路由以实现针对性功能。
来自Context7 - 路由中间件:
javascript
const express = require('express');
const app = express();
const port = 3000;
// Middleware function
const requestLogger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
};
// Apply middleware to a specific route
app.get('/protected', requestLogger, (req, res) => {
res.send('This route is protected by middleware!');
});
// Apply middleware to multiple routes
const adminMiddleware = (req, res, next) => {
console.log('Admin access check...');
// In a real app, you'd check user roles here
next();
};
app.get('/admin/dashboard', adminMiddleware, (req, res) => {
res.send('Welcome to the admin dashboard!');
});
// Middleware applied globally
app.use(requestLogger);
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Route middleware example listening at http://localhost:${port}`);
});Authentication Middleware
认证中间件
javascript
const jwt = require('jsonwebtoken');
// JWT Authentication Middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
};
// Role-based Authorization Middleware
const authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Usage
app.get('/admin/users', authenticateToken, authorize('admin'), (req, res) => {
res.json({ users: [] });
});javascript
const jwt = require('jsonwebtoken');
// JWT Authentication Middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
};
// Role-based Authorization Middleware
const authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Usage
app.get('/admin/users', authenticateToken, authorize('admin'), (req, res) => {
res.json({ users: [] });
});Request Validation Middleware
请求验证中间件
javascript
const { body, param, query, validationResult } = require('express-validator');
// Validation middleware factory
const validate = (validations) => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
};
};
// Usage
app.post('/users', validate([
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
body('name').trim().notEmpty()
]), (req, res) => {
// Request is validated
res.json({ success: true });
});javascript
const { body, param, query, validationResult } = require('express-validator');
// Validation middleware factory
const validate = (validations) => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
};
};
// Usage
app.post('/users', validate([
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
body('name').trim().notEmpty()
]), (req, res) => {
// Request is validated
res.json({ success: true });
});Rate Limiting Middleware
速率限制中间件
javascript
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
// In-memory rate limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
// Redis-based rate limiter for distributed systems
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
});
const distributedLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:',
}),
windowMs: 15 * 60 * 1000,
max: 100,
});
// Apply to all routes
app.use('/api/', distributedLimiter);
// Apply to specific routes
app.post('/api/login', rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15 minutes
}), loginHandler);javascript
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
// In-memory rate limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
// Redis-based rate limiter for distributed systems
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
});
const distributedLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:',
}),
windowMs: 15 * 60 * 1000,
max: 100,
});
// Apply to all routes
app.use('/api/', distributedLimiter);
// Apply to specific routes
app.post('/api/login', rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15 minutes
}), loginHandler);Logging Middleware
日志中间件
javascript
const morgan = require('morgan');
const winston = require('winston');
const { format } = winston;
// Create Winston logger
const logger = winston.createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.json()
),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// Console logging in development
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
}));
}
// HTTP request logging with Morgan
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim())
}
}));
// Custom logging middleware
const requestLogger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get('user-agent')
});
});
next();
};
app.use(requestLogger);javascript
const morgan = require('morgan');
const winston = require('winston');
const { format } = winston;
// Create Winston logger
const logger = winston.createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.json()
),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// Console logging in development
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
}));
}
// HTTP request logging with Morgan
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim())
}
}));
// Custom logging middleware
const requestLogger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get('user-agent')
});
});
next();
};
app.use(requestLogger);CORS Middleware
CORS中间件
javascript
const cors = require('cors');
// Basic CORS
app.use(cors());
// Configured CORS
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://example.com',
'https://app.example.com',
process.env.FRONTEND_URL
];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
maxAge: 86400, // 24 hours
};
app.use(cors(corsOptions));
// CORS for specific routes
app.options('/api/admin/*', cors(adminCorsOptions));
app.use('/api/admin/', cors(adminCorsOptions));javascript
const cors = require('cors');
// Basic CORS
app.use(cors());
// Configured CORS
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://example.com',
'https://app.example.com',
process.env.FRONTEND_URL
];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
maxAge: 86400, // 24 hours
};
app.use(cors(corsOptions));
// CORS for specific routes
app.options('/api/admin/*', cors(adminCorsOptions));
app.use('/api/admin/', cors(adminCorsOptions));Security Middleware
安全中间件
javascript
const helmet = require('helmet');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
// Helmet - Set security headers
app.use(helmet());
// Custom security headers
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// Sanitize data against NoSQL injection
app.use(mongoSanitize());
// Prevent XSS attacks
app.use(xss());
// Prevent HTTP Parameter Pollution
app.use(hpp({
whitelist: ['sort', 'fields', 'page', 'limit']
}));
// Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
}));javascript
const helmet = require('helmet');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
// Helmet - Set security headers
app.use(helmet());
// Custom security headers
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// Sanitize data against NoSQL injection
app.use(mongoSanitize());
// Prevent XSS attacks
app.use(xss());
// Prevent HTTP Parameter Pollution
app.use(hpp({
whitelist: ['sort', 'fields', 'page', 'limit']
}));
// Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
}));Routing Strategies
路由策略
Basic Routing
基础路由
From Context7 - Express Routing:
javascript
app.get('/', home);
app.use('/public', require('st')(process.cwd()));
app.get('/users', users.list);
app.post('/users', users.create);来自Context7 - Express路由:
javascript
app.get('/', home);
app.use('/public', require('st')(process.cwd()));
app.get('/users', users.list);
app.post('/users', users.create);Route Method Chaining
路由方法链式调用
From Context7 - Route Chaining:
javascript
app.route('/users')
.get(function(req, res, next) {
// Get all users
res.json({ users: [] });
})
.post(function(req, res, next) {
// Create new user
res.status(201).json({ user: {} });
});来自Context7 - 路由链式调用:
javascript
app.route('/users')
.get(function(req, res, next) {
// Get all users
res.json({ users: [] });
})
.post(function(req, res, next) {
// Create new user
res.status(201).json({ user: {} });
});Router Modules
路由模块
javascript
// routes/users.js
const express = require('express');
const router = express.Router();
// Middleware specific to this router
router.use((req, res, next) => {
console.log('Time: ', Date.now());
next();
});
// Define routes
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.get('/:id', (req, res) => {
res.json({ user: { id: req.params.id } });
});
router.post('/', (req, res) => {
res.status(201).json({ user: req.body });
});
router.put('/:id', (req, res) => {
res.json({ user: { id: req.params.id, ...req.body } });
});
router.delete('/:id', (req, res) => {
res.status(204).send();
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);javascript
// routes/users.js
const express = require('express');
const router = express.Router();
// Middleware specific to this router
router.use((req, res, next) => {
console.log('Time: ', Date.now());
next();
});
// Define routes
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.get('/:id', (req, res) => {
res.json({ user: { id: req.params.id } });
});
router.post('/', (req, res) => {
res.status(201).json({ user: req.body });
});
router.put('/:id', (req, res) => {
res.json({ user: { id: req.params.id, ...req.body } });
});
router.delete('/:id', (req, res) => {
res.status(204).send();
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);Route Parameters
路由参数
javascript
// Named parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
// Parameter middleware
app.param('userId', (req, res, next, userId) => {
// Fetch user from database
User.findById(userId)
.then(user => {
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
req.user = user;
next();
})
.catch(next);
});
// Multiple callbacks
app.param('postId', [
validatePostId,
fetchPost,
checkPermissions
]);javascript
// Named parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
// Parameter middleware
app.param('userId', (req, res, next, userId) => {
// Fetch user from database
User.findById(userId)
.then(user => {
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
req.user = user;
next();
})
.catch(next);
});
// Multiple callbacks
app.param('postId', [
validatePostId,
fetchPost,
checkPermissions
]);Query Parameters
查询参数
javascript
// GET /api/users?role=admin&active=true&page=2&limit=10
app.get('/api/users', (req, res) => {
const {
role,
active,
page = 1,
limit = 10,
sort = '-createdAt'
} = req.query;
const query = {};
if (role) query.role = role;
if (active !== undefined) query.active = active === 'true';
const skip = (page - 1) * limit;
User.find(query)
.sort(sort)
.limit(parseInt(limit))
.skip(skip)
.then(users => res.json({ users, page, limit }))
.catch(next);
});javascript
// GET /api/users?role=admin&active=true&page=2&limit=10
app.get('/api/users', (req, res) => {
const {
role,
active,
page = 1,
limit = 10,
sort = '-createdAt'
} = req.query;
const query = {};
if (role) query.role = role;
if (active !== undefined) query.active = active === 'true';
const skip = (page - 1) * limit;
User.find(query)
.sort(sort)
.limit(parseInt(limit))
.skip(skip)
.then(users => res.json({ users, page, limit }))
.catch(next);
});API Versioning
API版本控制
javascript
// Version 1 routes
const v1Router = express.Router();
v1Router.get('/users', (req, res) => {
res.json({ version: 'v1', users: [] });
});
// Version 2 routes
const v2Router = express.Router();
v2Router.get('/users', (req, res) => {
res.json({ version: 'v2', users: [], meta: {} });
});
// Mount versioned routes
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// Header-based versioning
app.use('/api/users', (req, res, next) => {
const version = req.headers['api-version'] || 'v1';
if (version === 'v2') {
return v2UsersHandler(req, res, next);
}
return v1UsersHandler(req, res, next);
});javascript
// Version 1 routes
const v1Router = express.Router();
v1Router.get('/users', (req, res) => {
res.json({ version: 'v1', users: [] });
});
// Version 2 routes
const v2Router = express.Router();
v2Router.get('/users', (req, res) => {
res.json({ version: 'v2', users: [], meta: {} });
});
// Mount versioned routes
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// Header-based versioning
app.use('/api/users', (req, res, next) => {
const version = req.headers['api-version'] || 'v1';
if (version === 'v2') {
return v2UsersHandler(req, res, next);
}
return v1UsersHandler(req, res, next);
});RESTful Route Organization
RESTful路由组织
javascript
// controllers/users.controller.js
class UsersController {
async list(req, res, next) {
try {
const users = await User.find();
res.json({ users });
} catch (error) {
next(error);
}
}
async get(req, res, next) {
try {
res.json({ user: req.user });
} catch (error) {
next(error);
}
}
async create(req, res, next) {
try {
const user = await User.create(req.body);
res.status(201).json({ user });
} catch (error) {
next(error);
}
}
async update(req, res, next) {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
res.json({ user });
} catch (error) {
next(error);
}
}
async delete(req, res, next) {
try {
await User.findByIdAndDelete(req.params.id);
res.status(204).send();
} catch (error) {
next(error);
}
}
}
// routes/users.routes.js
const router = express.Router();
const controller = new UsersController();
router.get('/', controller.list);
router.get('/:id', controller.get);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.delete('/:id', controller.delete);
module.exports = router;javascript
// controllers/users.controller.js
class UsersController {
async list(req, res, next) {
try {
const users = await User.find();
res.json({ users });
} catch (error) {
next(error);
}
}
async get(req, res, next) {
try {
res.json({ user: req.user });
} catch (error) {
next(error);
}
}
async create(req, res, next) {
try {
const user = await User.create(req.body);
res.status(201).json({ user });
} catch (error) {
next(error);
}
}
async update(req, res, next) {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
res.json({ user });
} catch (error) {
next(error);
}
}
async delete(req, res, next) {
try {
await User.findByIdAndDelete(req.params.id);
res.status(204).send();
} catch (error) {
next(error);
}
}
}
// routes/users.routes.js
const router = express.Router();
const controller = new UsersController();
router.get('/', controller.list);
router.get('/:id', controller.get);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.delete('/:id', controller.delete);
module.exports = router;Scalability Patterns
可扩展性模式
Horizontal Scaling
水平扩展
Deploy multiple instances of your service behind a load balancer.
javascript
// Enable cluster mode
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master process ${process.pid} is running`);
console.log(`Forking ${numCPUs} workers...`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Restarting...`);
cluster.fork();
});
} else {
// Workers share the TCP connection
const app = require('./app');
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Worker ${process.pid} started on port ${port}`);
});
}在负载均衡器后部署多个服务实例。
javascript
// Enable cluster mode
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master process ${process.pid} is running`);
console.log(`Forking ${numCPUs} workers...`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Restarting...`);
cluster.fork();
});
} else {
// Workers share the TCP connection
const app = require('./app');
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Worker ${process.pid} started on port ${port}`);
});
}Load Balancing
负载均衡
nginx
undefinednginx
undefinednginx.conf
nginx.conf
upstream backend {
least_conn;
server localhost:3001;
server localhost:3002;
server localhost:3003;
server localhost:3004;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}}
undefinedupstream backend {
least_conn;
server localhost:3001;
server localhost:3002;
server localhost:3003;
server localhost:3004;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}}
undefinedCaching Strategies
缓存策略
javascript
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
});
// Cache middleware
const cacheMiddleware = (duration = 300) => {
return async (req, res, next) => {
if (req.method !== 'GET') {
return next();
}
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
// Store original res.json
const originalJson = res.json.bind(res);
// Override res.json
res.json = (body) => {
redis.setex(key, duration, JSON.stringify(body));
return originalJson(body);
};
next();
} catch (error) {
console.error('Cache error:', error);
next();
}
};
};
// Usage
app.get('/api/products', cacheMiddleware(600), async (req, res) => {
const products = await Product.find();
res.json({ products });
});
// Cache invalidation
const invalidateCache = async (pattern) => {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
};
// Invalidate on updates
app.post('/api/products', async (req, res) => {
const product = await Product.create(req.body);
await invalidateCache('cache:/api/products*');
res.status(201).json({ product });
});javascript
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
});
// Cache middleware
const cacheMiddleware = (duration = 300) => {
return async (req, res, next) => {
if (req.method !== 'GET') {
return next();
}
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
// Store original res.json
const originalJson = res.json.bind(res);
// Override res.json
res.json = (body) => {
redis.setex(key, duration, JSON.stringify(body));
return originalJson(body);
};
next();
} catch (error) {
console.error('Cache error:', error);
next();
}
};
};
// Usage
app.get('/api/products', cacheMiddleware(600), async (req, res) => {
const products = await Product.find();
res.json({ products });
});
// Cache invalidation
const invalidateCache = async (pattern) => {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
};
// Invalidate on updates
app.post('/api/products', async (req, res) => {
const product = await Product.create(req.body);
await invalidateCache('cache:/api/products*');
res.status(201).json({ product });
});Database Connection Pooling
数据库连接池
javascript
const mongoose = require('mongoose');
// MongoDB connection with pooling
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
poolSize: 10, // Maintain up to 10 socket connections
socketTimeoutMS: 45000,
family: 4,
});
// PostgreSQL with connection pooling
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20, // Maximum number of clients
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Query helper
const query = async (text, params) => {
const start = Date.now();
const res = await pool.query(text, params);
const duration = Date.now() - start;
console.log('Executed query', { text, duration, rows: res.rowCount });
return res;
};
module.exports = { query, pool };javascript
const mongoose = require('mongoose');
// MongoDB connection with pooling
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
poolSize: 10, // Maintain up to 10 socket connections
socketTimeoutMS: 45000,
family: 4,
});
// PostgreSQL with connection pooling
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20, // Maximum number of clients
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Query helper
const query = async (text, params) => {
const start = Date.now();
const res = await pool.query(text, params);
const duration = Date.now() - start;
console.log('Executed query', { text, duration, rows: res.rowCount });
return res;
};
module.exports = { query, pool };Response Compression
响应压缩
javascript
const compression = require('compression');
// Basic compression
app.use(compression());
// Custom compression settings
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6, // Compression level (0-9)
threshold: 1024, // Minimum size to compress (bytes)
}));javascript
const compression = require('compression');
// Basic compression
app.use(compression());
// Custom compression settings
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6, // Compression level (0-9)
threshold: 1024, // Minimum size to compress (bytes)
}));Request Throttling
请求限流
javascript
const { Throttle } = require('stream-throttle');
// Throttle large responses
app.get('/api/large-dataset', (req, res) => {
const dataStream = getLargeDataStream();
// Throttle to 1MB/s
const throttle = new Throttle({ rate: 1024 * 1024 });
res.setHeader('Content-Type', 'application/json');
dataStream.pipe(throttle).pipe(res);
});javascript
const { Throttle } = require('stream-throttle');
// Throttle large responses
app.get('/api/large-dataset', (req, res) => {
const dataStream = getLargeDataStream();
// Throttle to 1MB/s
const throttle = new Throttle({ rate: 1024 * 1024 });
res.setHeader('Content-Type', 'application/json');
dataStream.pipe(throttle).pipe(res);
});Production Architecture
生产架构
Docker Containerization
Docker容器化
dockerfile
undefineddockerfile
undefinedDockerfile
Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
FROM node:18-alpine AS builder
WORKDIR /app
Copy package files
Copy package files
COPY package*.json ./
COPY package*.json ./
Install dependencies
Install dependencies
RUN npm ci --only=production
RUN npm ci --only=production
Copy source code
Copy source code
COPY . .
COPY . .
Production image
Production image
FROM node:18-alpine
WORKDIR /app
FROM node:18-alpine
WORKDIR /app
Create non-root user
Create non-root user
RUN addgroup -g 1001 -S nodejs &&
adduser -S nodejs -u 1001
adduser -S nodejs -u 1001
RUN addgroup -g 1001 -S nodejs &&
adduser -S nodejs -u 1001
adduser -S nodejs -u 1001
Copy from builder
Copy from builder
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs . .
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs . .
Switch to non-root user
Switch to non-root user
USER nodejs
USER nodejs
Expose port
Expose port
EXPOSE 3000
EXPOSE 3000
Health check
Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s
CMD node healthcheck.js
CMD node healthcheck.js
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s
CMD node healthcheck.js
CMD node healthcheck.js
Start application
Start application
CMD ["node", "server.js"]
```yamlCMD ["node", "server.js"]
```yamldocker-compose.yml
docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- REDIS_HOST=redis
- MONGODB_URI=mongodb://mongo:27017/app
depends_on:
- redis
- mongo
restart: unless-stopped
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
restart: unless-stopped
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- api
restart: unless-stopped
volumes:
redis-data:
mongo-data:
undefinedversion: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- REDIS_HOST=redis
- MONGODB_URI=mongodb://mongo:27017/app
depends_on:
- redis
- mongo
restart: unless-stopped
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
restart: unless-stopped
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- api
restart: unless-stopped
volumes:
redis-data:
mongo-data:
undefinedProcess Management with PM2
使用PM2进行进程管理
javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'api',
script: './server.js',
instances: 'max',
exec_mode: 'cluster',
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true,
merge_logs: true,
}]
};javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'api',
script: './server.js',
instances: 'max',
exec_mode: 'cluster',
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true,
merge_logs: true,
}]
};Health Checks
健康检查
javascript
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: process.env.PORT || 3000,
path: '/health',
timeout: 2000
};
const healthCheck = http.request(options, (res) => {
console.log(`HEALTHCHECK STATUS: ${res.statusCode}`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
healthCheck.on('error', (err) => {
console.error('ERROR:', err);
process.exit(1);
});
healthCheck.end();
// Health endpoint
app.get('/health', async (req, res) => {
const health = {
uptime: process.uptime(),
message: 'OK',
timestamp: Date.now()
};
try {
// Check database connection
await mongoose.connection.db.admin().ping();
health.database = 'connected';
// Check Redis connection
await redis.ping();
health.cache = 'connected';
res.status(200).json(health);
} catch (error) {
health.message = error.message;
res.status(503).json(health);
}
});
// Readiness check
app.get('/ready', (req, res) => {
res.status(200).json({ ready: true });
});
// Liveness check
app.get('/live', (req, res) => {
res.status(200).json({ alive: true });
});javascript
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: process.env.PORT || 3000,
path: '/health',
timeout: 2000
};
const healthCheck = http.request(options, (res) => {
console.log(`HEALTHCHECK STATUS: ${res.statusCode}`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
healthCheck.on('error', (err) => {
console.error('ERROR:', err);
process.exit(1);
});
healthCheck.end();
// Health endpoint
app.get('/health', async (req, res) => {
const health = {
uptime: process.uptime(),
message: 'OK',
timestamp: Date.now()
};
try {
// Check database connection
await mongoose.connection.db.admin().ping();
health.database = 'connected';
// Check Redis connection
await redis.ping();
health.cache = 'connected';
res.status(200).json(health);
} catch (error) {
health.message = error.message;
res.status(503).json(health);
}
});
// Readiness check
app.get('/ready', (req, res) => {
res.status(200).json({ ready: true });
});
// Liveness check
app.get('/live', (req, res) => {
res.status(200).json({ alive: true });
});Graceful Shutdown
优雅关闭
javascript
// server.js
const gracefulShutdown = () => {
console.log('Received shutdown signal, closing server gracefully...');
server.close(async () => {
console.log('HTTP server closed');
try {
// Close database connections
await mongoose.connection.close();
console.log('MongoDB connection closed');
// Close Redis connection
await redis.quit();
console.log('Redis connection closed');
// Close other resources
// ...
console.log('Graceful shutdown completed');
process.exit(0);
} catch (err) {
console.error('Error during shutdown:', err);
process.exit(1);
}
});
// Force shutdown after 10 seconds
setTimeout(() => {
console.error('Forcing shutdown after timeout');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);javascript
// server.js
const gracefulShutdown = () => {
console.log('Received shutdown signal, closing server gracefully...');
server.close(async () => {
console.log('HTTP server closed');
try {
// Close database connections
await mongoose.connection.close();
console.log('MongoDB connection closed');
// Close Redis connection
await redis.quit();
console.log('Redis connection closed');
// Close other resources
// ...
console.log('Graceful shutdown completed');
process.exit(0);
} catch (err) {
console.error('Error during shutdown:', err);
process.exit(1);
}
});
// Force shutdown after 10 seconds
setTimeout(() => {
console.error('Forcing shutdown after timeout');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);Monitoring and Observability
监控与可观测性
javascript
const promClient = require('prom-client');
// Create a Registry
const register = new promClient.Registry();
// Add default metrics
promClient.collectDefaultMetrics({ register });
// Custom metrics
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.5, 1, 2, 5]
});
const httpRequestTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpRequestDuration);
register.registerMetric(httpRequestTotal);
// Metrics middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route ? req.route.path : req.path;
httpRequestDuration.labels(req.method, route, res.statusCode).observe(duration);
httpRequestTotal.labels(req.method, route, res.statusCode).inc();
});
next();
});
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});javascript
const promClient = require('prom-client');
// Create a Registry
const register = new promClient.Registry();
// Add default metrics
promClient.collectDefaultMetrics({ register });
// Custom metrics
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.5, 1, 2, 5]
});
const httpRequestTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpRequestDuration);
register.registerMetric(httpRequestTotal);
// Metrics middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route ? req.route.path : req.path;
httpRequestDuration.labels(req.method, route, res.statusCode).observe(duration);
httpRequestTotal.labels(req.method, route, res.statusCode).inc();
});
next();
});
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});Distributed Tracing
分布式追踪
javascript
const { trace, context } = require('@opentelemetry/api');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
// Create tracer provider
const provider = new NodeTracerProvider();
provider.register();
// Register instrumentations
registerInstrumentations({
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
],
});
const tracer = trace.getTracer('user-service');
// Custom tracing middleware
const tracingMiddleware = (req, res, next) => {
const span = tracer.startSpan(`HTTP ${req.method} ${req.path}`);
span.setAttributes({
'http.method': req.method,
'http.url': req.url,
'http.user_agent': req.get('user-agent'),
});
res.on('finish', () => {
span.setAttributes({
'http.status_code': res.statusCode,
});
span.end();
});
next();
};
app.use(tracingMiddleware);javascript
const { trace, context } = require('@opentelemetry/api');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
// Create tracer provider
const provider = new NodeTracerProvider();
provider.register();
// Register instrumentations
registerInstrumentations({
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
],
});
const tracer = trace.getTracer('user-service');
// Custom tracing middleware
const tracingMiddleware = (req, res, next) => {
const span = tracer.startSpan(`HTTP ${req.method} ${req.path}`);
span.setAttributes({
'http.method': req.method,
'http.url': req.url,
'http.user_agent': req.get('user-agent'),
});
res.on('finish', () => {
span.setAttributes({
'http.status_code': res.statusCode,
});
span.end();
});
next();
};
app.use(tracingMiddleware);Best Practices
最佳实践
Project Structure
项目结构
express-microservice/
├── src/
│ ├── config/
│ │ ├── database.js
│ │ ├── redis.js
│ │ └── logger.js
│ ├── controllers/
│ │ ├── users.controller.js
│ │ └── auth.controller.js
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── validation.js
│ │ ├── errorHandler.js
│ │ └── requestLogger.js
│ ├── models/
│ │ └── user.model.js
│ ├── routes/
│ │ ├── index.js
│ │ ├── users.routes.js
│ │ └── auth.routes.js
│ ├── services/
│ │ ├── users.service.js
│ │ ├── auth.service.js
│ │ └── email.service.js
│ ├── utils/
│ │ ├── apiError.js
│ │ ├── catchAsync.js
│ │ └── validators.js
│ ├── app.js
│ └── server.js
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
├── .env
├── .env.example
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── ecosystem.config.js
├── package.json
└── README.mdexpress-microservice/
├── src/
│ ├── config/
│ │ ├── database.js
│ │ ├── redis.js
│ │ └── logger.js
│ ├── controllers/
│ │ ├── users.controller.js
│ │ └── auth.controller.js
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── validation.js
│ │ ├── errorHandler.js
│ │ └── requestLogger.js
│ ├── models/
│ │ └── user.model.js
│ ├── routes/
│ │ ├── index.js
│ │ ├── users.routes.js
│ │ └── auth.routes.js
│ ├── services/
│ │ ├── users.service.js
│ │ ├── auth.service.js
│ │ └── email.service.js
│ ├── utils/
│ │ ├── apiError.js
│ │ ├── catchAsync.js
│ │ └── validators.js
│ ├── app.js
│ └── server.js
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
├── .env
├── .env.example
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── ecosystem.config.js
├── package.json
└── README.mdEnvironment Configuration
环境配置
javascript
// config/env.js
const dotenv = require('dotenv');
const Joi = require('joi');
dotenv.config();
const envSchema = Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
MONGODB_URI: Joi.string().required(),
REDIS_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().default(6379),
JWT_SECRET: Joi.string().required(),
JWT_EXPIRES_IN: Joi.string().default('7d'),
LOG_LEVEL: Joi.string()
.valid('error', 'warn', 'info', 'debug')
.default('info'),
}).unknown();
const { value: env, error } = envSchema.validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
module.exports = {
env: env.NODE_ENV,
port: env.PORT,
mongodb: {
uri: env.MONGODB_URI,
},
redis: {
host: env.REDIS_HOST,
port: env.REDIS_PORT,
},
jwt: {
secret: env.JWT_SECRET,
expiresIn: env.JWT_EXPIRES_IN,
},
logging: {
level: env.LOG_LEVEL,
},
};javascript
// config/env.js
const dotenv = require('dotenv');
const Joi = require('joi');
dotenv.config();
const envSchema = Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
MONGODB_URI: Joi.string().required(),
REDIS_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().default(6379),
JWT_SECRET: Joi.string().required(),
JWT_EXPIRES_IN: Joi.string().default('7d'),
LOG_LEVEL: Joi.string()
.valid('error', 'warn', 'info', 'debug')
.default('info'),
}).unknown();
const { value: env, error } = envSchema.validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
module.exports = {
env: env.NODE_ENV,
port: env.PORT,
mongodb: {
uri: env.MONGODB_URI,
},
redis: {
host: env.REDIS_HOST,
port: env.REDIS_PORT,
},
jwt: {
secret: env.JWT_SECRET,
expiresIn: env.JWT_EXPIRES_IN,
},
logging: {
level: env.LOG_LEVEL,
},
};Error Handling Best Practices
错误处理最佳实践
javascript
// utils/apiError.js
class ApiError extends Error {
constructor(statusCode, message, isOperational = true, stack = '') {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}
module.exports = ApiError;
// utils/catchAsync.js
const catchAsync = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
module.exports = catchAsync;
// middleware/errorHandler.js
const config = require('../config/env');
const logger = require('../config/logger');
const ApiError = require('../utils/apiError');
const errorConverter = (err, req, res, next) => {
let error = err;
if (!(error instanceof ApiError)) {
const statusCode = error.statusCode || 500;
const message = error.message || 'Internal Server Error';
error = new ApiError(statusCode, message, false, err.stack);
}
next(error);
};
const errorHandler = (err, req, res, next) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = 500;
message = 'Internal Server Error';
}
res.locals.errorMessage = err.message;
const response = {
code: statusCode,
message,
...(config.env === 'development' && { stack: err.stack }),
};
if (config.env === 'development') {
logger.error(err);
}
res.status(statusCode).json(response);
};
module.exports = {
errorConverter,
errorHandler,
};javascript
// utils/apiError.js
class ApiError extends Error {
constructor(statusCode, message, isOperational = true, stack = '') {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}
module.exports = ApiError;
// utils/catchAsync.js
const catchAsync = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
module.exports = catchAsync;
// middleware/errorHandler.js
const config = require('../config/env');
const logger = require('../config/logger');
const ApiError = require('../utils/apiError');
const errorConverter = (err, req, res, next) => {
let error = err;
if (!(error instanceof ApiError)) {
const statusCode = error.statusCode || 500;
const message = error.message || 'Internal Server Error';
error = new ApiError(statusCode, message, false, err.stack);
}
next(error);
};
const errorHandler = (err, req, res, next) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = 500;
message = 'Internal Server Error';
}
res.locals.errorMessage = err.message;
const response = {
code: statusCode,
message,
...(config.env === 'development' && { stack: err.stack }),
};
if (config.env === 'development') {
logger.error(err);
}
res.status(statusCode).json(response);
};
module.exports = {
errorConverter,
errorHandler,
};Testing Strategies
测试策略
javascript
// tests/integration/users.test.js
const request = require('supertest');
const app = require('../../src/app');
const { User } = require('../../src/models');
describe('User API', () => {
beforeEach(async () => {
await User.deleteMany({});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
};
const res = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(res.body).toHaveProperty('user');
expect(res.body.user).toHaveProperty('id');
expect(res.body.user.email).toBe(userData.email);
expect(res.body.user).not.toHaveProperty('password');
});
it('should return 400 for invalid email', async () => {
const userData = {
name: 'John Doe',
email: 'invalid-email',
password: 'password123',
};
const res = await request(app)
.post('/api/users')
.send(userData)
.expect(400);
expect(res.body).toHaveProperty('error');
});
});
describe('GET /api/users/:id', () => {
it('should return user by id', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
password: 'hashedpassword',
});
const res = await request(app)
.get(`/api/users/${user.id}`)
.expect(200);
expect(res.body.user.id).toBe(user.id);
});
it('should return 404 for non-existent user', async () => {
await request(app)
.get('/api/users/507f1f77bcf86cd799439011')
.expect(404);
});
});
});javascript
// tests/integration/users.test.js
const request = require('supertest');
const app = require('../../src/app');
const { User } = require('../../src/models');
describe('User API', () => {
beforeEach(async () => {
await User.deleteMany({});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
};
const res = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(res.body).toHaveProperty('user');
expect(res.body.user).toHaveProperty('id');
expect(res.body.user.email).toBe(userData.email);
expect(res.body.user).not.toHaveProperty('password');
});
it('should return 400 for invalid email', async () => {
const userData = {
name: 'John Doe',
email: 'invalid-email',
password: 'password123',
};
const res = await request(app)
.post('/api/users')
.send(userData)
.expect(400);
expect(res.body).toHaveProperty('error');
});
});
describe('GET /api/users/:id', () => {
it('should return user by id', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
password: 'hashedpassword',
});
const res = await request(app)
.get(`/api/users/${user.id}`)
.expect(200);
expect(res.body.user.id).toBe(user.id);
});
it('should return 404 for non-existent user', async () => {
await request(app)
.get('/api/users/507f1f77bcf86cd799439011')
.expect(404);
});
});
});Performance Optimization
性能优化
javascript
// Enable gzip compression
const compression = require('compression');
app.use(compression());
// Use efficient JSON parsing
app.use(express.json({ limit: '10mb' }));
// Database query optimization
const getUsers = async (filters) => {
return User.find(filters)
.select('name email role') // Select only needed fields
.lean() // Return plain objects instead of Mongoose documents
.limit(100);
};
// Implement pagination
const paginateResults = async (model, page = 1, limit = 10) => {
const skip = (page - 1) * limit;
const [results, total] = await Promise.all([
model.find().skip(skip).limit(limit).lean(),
model.countDocuments(),
]);
return {
results,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
};
};
// Use indexes
userSchema.index({ email: 1 });
userSchema.index({ role: 1, createdAt: -1 });
// Connection pooling and keep-alive
const agent = new http.Agent({
keepAlive: true,
maxSockets: 50,
});javascript
// Enable gzip compression
const compression = require('compression');
app.use(compression());
// Use efficient JSON parsing
app.use(express.json({ limit: '10mb' }));
// Database query optimization
const getUsers = async (filters) => {
return User.find(filters)
.select('name email role') // Select only needed fields
.lean() // Return plain objects instead of Mongoose documents
.limit(100);
};
// Implement pagination
const paginateResults = async (model, page = 1, limit = 10) => {
const skip = (page - 1) * limit;
const [results, total] = await Promise.all([
model.find().skip(skip).limit(limit).lean(),
model.countDocuments(),
]);
return {
results,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
};
};
// Use indexes
userSchema.index({ email: 1 });
userSchema.index({ role: 1, createdAt: -1 });
// Connection pooling and keep-alive
const agent = new http.Agent({
keepAlive: true,
maxSockets: 50,
});Security Best Practices
安全最佳实践
javascript
// Validate and sanitize inputs
const { body } = require('express-validator');
const userValidation = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).trim(),
body('name').trim().escape(),
];
// Implement rate limiting
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true,
});
app.post('/api/auth/login', loginLimiter, loginHandler);
// Use parameterized queries
const getUserByEmail = async (email) => {
return User.findOne({ email }); // Protected against NoSQL injection
};
// Implement CSRF protection
const csrf = require('csurf');
app.use(csrf({ cookie: true }));
// Set secure cookies
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Hash passwords
const bcrypt = require('bcrypt');
const hashPassword = async (password) => {
return bcrypt.hash(password, 12);
};javascript
// Validate and sanitize inputs
const { body } = require('express-validator');
const userValidation = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).trim(),
body('name').trim().escape(),
];
// Implement rate limiting
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true,
});
app.post('/api/auth/login', loginLimiter, loginHandler);
// Use parameterized queries
const getUserByEmail = async (email) => {
return User.findOne({ email }); // Protected against NoSQL injection
};
// Implement CSRF protection
const csrf = require('csurf');
app.use(csrf({ cookie: true }));
// Set secure cookies
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Hash passwords
const bcrypt = require('bcrypt');
const hashPassword = async (password) => {
return bcrypt.hash(password, 12);
};Examples
示例
Example 1: Basic Express Microservice
示例1:基础Express微服务
javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const app = express();
// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
// Routes
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const app = express();
// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
// Routes
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Example 2: Authentication Service
示例2:认证服务
javascript
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator');
const router = express.Router();
// Register
router.post('/register',
body('email').isEmail(),
body('password').isLength({ min: 8 }),
async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({ error: 'User already exists' });
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Create user
const user = await User.create({
email,
password: hashedPassword,
});
// Generate token
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.status(201).json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
next(error);
}
}
);
// Login
router.post('/login',
body('email').isEmail(),
body('password').exists(),
async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
next(error);
}
}
);
module.exports = router;javascript
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator');
const router = express.Router();
// Register
router.post('/register',
body('email').isEmail(),
body('password').isLength({ min: 8 }),
async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({ error: 'User already exists' });
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Create user
const user = await User.create({
email,
password: hashedPassword,
});
// Generate token
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.status(201).json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
next(error);
}
}
);
// Login
router.post('/login',
body('email').isEmail(),
body('password').exists(),
async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
next(error);
}
}
);
module.exports = router;Example 3: API Gateway Pattern
示例3:API网关模式
javascript
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// Service discovery (simplified)
const services = {
users: 'http://users-service:3001',
products: 'http://products-service:3002',
orders: 'http://orders-service:3003',
};
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});
app.use(limiter);
// Proxy routes
app.use('/api/users', authenticate, createProxyMiddleware({
target: services.users,
changeOrigin: true,
pathRewrite: { '^/api/users': '' },
}));
app.use('/api/products', createProxyMiddleware({
target: services.products,
changeOrigin: true,
pathRewrite: { '^/api/products': '' },
}));
app.use('/api/orders', authenticate, createProxyMiddleware({
target: services.orders,
changeOrigin: true,
pathRewrite: { '^/api/orders': '' },
}));
// Error handling
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Gateway error' });
});
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
console.log(`API Gateway running on port ${PORT}`);
});See EXAMPLES.md for 15+ additional comprehensive examples including circuit breakers, event-driven patterns, service mesh integration, and more.
Skill Version: 1.0.0
Last Updated: October 2025
Skill Category: Microservices, Backend Development, Node.js, Production Architecture
Compatible With: Express.js 4.x/5.x, Node.js 16+, Docker, Kubernetes
javascript
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// Service discovery (simplified)
const services = {
users: 'http://users-service:3001',
products: 'http://products-service:3002',
orders: 'http://orders-service:3003',
};
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});
app.use(limiter);
// Proxy routes
app.use('/api/users', authenticate, createProxyMiddleware({
target: services.users,
changeOrigin: true,
pathRewrite: { '^/api/users': '' },
}));
app.use('/api/products', createProxyMiddleware({
target: services.products,
changeOrigin: true,
pathRewrite: { '^/api/products': '' },
}));
app.use('/api/orders', authenticate, createProxyMiddleware({
target: services.orders,
changeOrigin: true,
pathRewrite: { '^/api/orders': '' },
}));
// Error handling
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Gateway error' });
});
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
console.log(`API Gateway running on port ${PORT}`);
});查看EXAMPLES.md获取15+个更多综合示例,包括断路器、事件驱动模式、服务网格集成等。
技能版本: 1.0.0
最后更新: 2025年10月
技能分类: 微服务, 后端开发, Node.js, 生产架构
兼容版本: Express.js 4.x/5.x, Node.js 16+, Docker, Kubernetes