某创业公司正在使用Node.js和Express开发一个提供API服务的后端平台。随着API数量的增长和多位开发者的加入,他们面临以下挑战:
- API设计不一致 - 不同开发者设计的API遵循不同的风格和模式
- 错误处理混乱 - 缺乏统一的错误处理机制,导致前端难以处理各种错误情况
- 安全性问题 - 常见的安全漏洞(如输入验证不足、缺少权限检查)频繁出现
- 性能监控缺失 - 无法有效识别和优化性能瓶颈
- 文档不完善 - API文档不完整或过时,增加了前后端协作的难度
为解决这些问题,团队设计了一系列Cursor规则来规范化Node.js后端API的开发:
<rule>
name: nodejs_api_route_structure
description: 确保Node.js API路由遵循RESTful设计原则和项目结构规范
filters:
# 匹配路由文件
- type: file_path
pattern: "(?:routes|controllers)/.*\\.js$"
# 匹配路由定义
- type: content
pattern: "router\\.(?:get|post|put|delete|patch)\\("
actions:
- type: review
criteria:
# 检查路由命名(使用复数名词)
- pattern: "router\\.(?:get|post|put|delete|patch)\\(['\"]/api/v\\d+/(?!health|auth|status)([a-z-]+)s(?:/|\\?|['\"\\)])"
message: "✓ 路由使用复数名词(符合RESTful规范)"
not_found_message: "✗ 资源路由应使用复数名词,如/api/v1/users而非/api/v1/user"
optional: true
# 检查版本号
- pattern: "router\\.(?:get|post|put|delete|patch)\\(['\"]/api/v\\d+/"
message: "✓ API包含版本号"
not_found_message: "✗ API路由应包含版本号,如/api/v1/"
# 检查控制器分离
- pattern: "router\\.(?:get|post|put|delete|patch)\\([^,]+,\\s*\\w+Controller\\."
message: "✓ 路由使用独立的控制器处理逻辑"
not_found_message: "✗ 路由处理逻辑应移至专门的控制器中"
- type: suggest
message: |
推荐的API路由结构:
```javascript
// routes/users.js
const express = require('express');
const router = express.Router();
const usersController = require('../controllers/users.controller');
const { authenticate, authorize } = require('../middlewares/auth');
/**
* @swagger
* /api/v1/users:
* get:
* description: 获取用户列表
* responses:
* 200:
* description: 返回用户列表
*/
router.get('/api/v1/users', authenticate, usersController.getUsers);
/**
* @swagger
* /api/v1/users/{id}:
* get:
* description: 通过ID获取用户
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: string
* responses:
* 200:
* description: 返回用户信息
* 404:
* description: 用户不存在
*/
router.get('/api/v1/users/:id', authenticate, usersController.getUserById);
// 其他CRUD操作...
module.exports = router;
```
RESTful API最佳实践:
1. 使用复数名词表示资源(users, products)
2. 使用HTTP方法表示操作(GET, POST, PUT, DELETE)
3. 使用嵌套路由表示关系(/users/:id/orders)
4. 包含API版本号(/api/v1/)
5. 将路由处理逻辑移至控制器中
metadata:
priority: high
version: 1.0.0
tags: ["nodejs", "api", "routing", "rest"]
</rule>
<rule>
name: nodejs_error_handling
description: 确保Node.js应用使用统一的错误处理机制
filters:
# 匹配Node.js文件
- type: file_extension
pattern: "\\.js$"
# 匹配错误处理相关内容
- type: content
pattern: "(?:try|catch|throw|error|AppError)"
actions:
- type: review
criteria:
# 检查自定义错误类
- pattern: "class\\s+(?:App|API|Custom|Base)?Error\\s+extends\\s+Error"
message: "✓ 使用自定义错误类"
not_found_message: "✗ 建议使用自定义错误类而非直接抛出Error"
optional: true
# 检查HTTP状态码
- pattern: "new\\s+(?:App|API|Custom|Base)?Error\\([^)]*?(?:\\d{3}|statusCode)"
message: "✓ 错误包含HTTP状态码"
not_found_message: "✗ 自定义错误应包含HTTP状态码"
optional: true
# 检查try-catch块
- pattern: "try\\s*{[^}]*}\\s*catch\\s*\\((?:err|error)\\)\\s*{"
message: "✓ 使用try-catch块处理错误"
not_found_message: "✗ 异步操作应使用try-catch块处理错误"
optional: true
- type: create_file
conditions:
- pattern: "class\\s+(?:App|API|Custom|Base)?Error"
not_found: true
path: "src/utils/errors.js"
content: |
/**
* 应用自定义错误类
* 用于统一错误处理和错误响应格式
*/
class AppError extends Error {
/**
* 创建应用错误
* @param {string} message - 错误消息
* @param {number} statusCode - HTTP状态码
* @param {string} code - 错误代码
* @param {*} details - 错误详情
*/
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR', details = null) {
super(message);
this.name = this.constructor.name;
this.statusCode = statusCode;
this.code = code;
this.details = details;
this.isOperational = true; // 标记为可操作性错误
// 捕获错误堆栈
Error.captureStackTrace(this, this.constructor);
}
}
/**
* 404错误 - 资源未找到
*/
class NotFoundError extends AppError {
constructor(message = '请求的资源不存在', details = null) {
super(message, 404, 'RESOURCE_NOT_FOUND', details);
}
}
/**
* 400错误 - 错误请求
*/
class BadRequestError extends AppError {
constructor(message = '无效的请求参数', details = null) {
super(message, 400, 'BAD_REQUEST', details);
}
}
/**
* 401错误 - 未授权
*/
class UnauthorizedError extends AppError {
constructor(message = '未授权访问', details = null) {
super(message, 401, 'UNAUTHORIZED', details);
}
}
/**
* 403错误 - 禁止访问
*/
class ForbiddenError extends AppError {
constructor(message = '禁止访问该资源', details = null) {
super(message, 403, 'FORBIDDEN', details);
}
}
/**
* 409错误 - 资源冲突
*/
class ConflictError extends AppError {
constructor(message = '资源冲突', details = null) {
super(message, 409, 'CONFLICT', details);
}
}
module.exports = {
AppError,
NotFoundError,
BadRequestError,
UnauthorizedError,
ForbiddenError,
ConflictError
};
- type: suggest
message: |
推荐的错误处理模式:
1. **创建统一错误处理中间件**:
```javascript
// middlewares/error.middleware.js
const { AppError } = require('../utils/errors');
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// 日志记录
console.error(`错误: ${err.stack}`);
// 默认错误响应
const response = {
success: false,
error: {
code: error.code || 'INTERNAL_ERROR',
message: error.message || '服务器内部错误',
}
};
// 添加错误详情(仅在开发环境)
if (process.env.NODE_ENV === 'development' && error.details) {
response.error.details = error.details;
}
// MongoDB错误处理示例
if (err.name === 'CastError') {
response.error.code = 'INVALID_ID';
response.error.message = '提供的ID格式不正确';
return res.status(400).json(response);
}
// 发送响应
return res.status(error.statusCode || 500).json(response);
};
module.exports = errorHandler;
```
2. **在控制器中使用自定义错误**:
```javascript
// controllers/users.controller.js
const { NotFoundError, BadRequestError } = require('../utils/errors');
exports.getUserById = async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('用户不存在', { id: req.params.id });
}
res.status(200).json({
success: true,
data: user
});
} catch (error) {
next(error); // 传递给错误处理中间件
}
};
```
3. **在应用程序入口添加错误处理中间件**:
```javascript
// app.js
const express = require('express');
const errorHandler = require('./middlewares/error.middleware');
const app = express();
// 路由...
// 错误处理中间件(放在所有路由之后)
app.use(errorHandler);
module.exports = app;
```
metadata:
priority: high
version: 1.0.0
tags: ["nodejs", "error-handling", "middleware"]
</rule>
<rule>
name: nodejs_api_security
description: 检查Node.js API的常见安全问题
filters:
# 匹配Node.js后端文件
- type: file_extension
pattern: "\\.js$"
# 匹配API相关文件
- type: file_path
pattern: "(?:routes|controllers|middlewares|app)\\.js$"
actions:
- type: review
criteria:
# 检查CORS配置
- pattern: "app\\.use\\(cors\\(\\{(?:[^}]*?(?:origin|methods|allowedHeaders|credentials)[^}]*?){1,}\\}\\)\\)"
message: "✓ 使用详细的CORS配置"
not_found_message: "✗ 应使用详细的CORS配置,而非默认配置"
optional: true
# 检查Helmet安全头
- pattern: "app\\.use\\(helmet\\(\\)\\)"
message: "✓ 使用Helmet设置安全HTTP头"
not_found_message: "✗ 应使用Helmet中间件设置安全HTTP头"
# 检查输入验证
- pattern: "(?:validator|joi|yup|express-validator|validate)"
message: "✓ 使用输入验证库"
not_found_message: "✗ 应使用输入验证库验证请求数据"
# 检查速率限制
- pattern: "(?:rate-limit|rateLimit)"
message: "✓ 实施API速率限制"
not_found_message: "✗ 应添加速率限制中间件防止暴力攻击"
# 检查敏感信息
- pattern: "process\\.env\\.(?:DB_URI|MONGODB_URI|DATABASE_URL)"
message: "✓ 使用环境变量存储敏感信息"
not_found_message: "✗ 敏感信息应存储在环境变量中"
- type: create_file
conditions:
- pattern: "app\\.use\\(helmet\\(\\)\\)"
not_found: true
- pattern: "app\\.use\\(cors\\("
not_found: true
path: "src/config/security.js"
content: |
/**
* API安全配置
*/
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const xss = require('xss-clean');
const hpp = require('hpp');
const mongoSanitize = require('express-mongo-sanitize');
/**
* 应用安全中间件
* @param {Express} app - Express应用实例
*/
const applySecurityMiddleware = (app) => {
// 设置安全HTTP头
app.use(helmet());
// 配置CORS
app.use(
cors({
origin: process.env.CORS_ORIGIN || '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
})
);
// 防止SQL注入和NoSQL查询注入
app.use(mongoSanitize());
// 防止XSS攻击
app.use(xss());
// 防止HTTP参数污染
app.use(hpp());
// 实施速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每IP 100次请求
standardHeaders: true,
message: {
success: false,
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: '请求过于频繁,请稍后再试'
}
}
});
app.use('/api', limiter);
return app;
};
module.exports = applySecurityMiddleware;
- type: suggest
message: |
Node.js API安全最佳实践:
1. **配置安全HTTP头(使用Helmet)**:
```javascript
// app.js
const helmet = require('helmet');
app.use(helmet());
```
2. **正确配置CORS**:
```javascript
const cors = require('cors');
app.use(cors({
origin: ['https://your-app.com', 'https://admin.your-app.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
```
3. **输入验证(所有用户输入都应验证)**:
```javascript
const { body, validationResult } = require('express-validator');
router.post('/api/v1/users',
[
body('email').isEmail().withMessage('请提供有效的邮箱'),
body('password').isLength({ min: 8 }).withMessage('密码至少8个字符')
],
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: '输入验证失败',
details: errors.array()
}
});
}
next();
},
userController.createUser
);
```
4. **防止参数污染和注入攻击**:
```javascript
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
app.use(mongoSanitize()); // 防止NoSQL注入
app.use(xss()); // 防止XSS攻击
app.use(hpp()); // 防止HTTP参数污染
```
5. **实施速率限制**:
```javascript
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每IP限制100次请求
message: '请求次数过多,请稍后再试'
});
app.use('/api', apiLimiter);
```
6. **使用环境变量存储敏感信息**:
```javascript
// 使用dotenv加载环境变量
require('dotenv').config();
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
```
metadata:
priority: critical
version: 1.0.0
tags: ["nodejs", "security", "api"]
</rule>
<rule>
name: nodejs_api_performance
description: 添加和检查Node.js API性能监控设置
filters:
# 匹配Node.js主应用文件
- type: file_path
pattern: "(?:app|server|index)\\.js$"
- type: event
pattern: "file_modify"
actions:
- type: review
criteria:
# 检查性能监控中间件
- pattern: "app\\.use\\(['\"]express-pino-logger['\"]|app\\.use\\(require\\(['\"]express-pino-logger['\"]\\)\\)|app\\.use\\(morgan\\(['\"]"
message: "✓ 使用API请求日志中间件"
not_found_message: "✗ 应使用请求日志中间件(如morgan或pino)"
# 检查响应时间监控
- pattern: "app\\.use\\(['\"]response-time['\"]|app\\.use\\(require\\(['\"]response-time['\"]\\)\\)"
message: "✓ 监控API响应时间"
not_found_message: "✗ 应使用response-time中间件监控响应时间"
optional: true
# 检查内存使用监控
- pattern: "require\\(['\"]node:process['\"]\\)|process\\.memoryUsage\\(\\)"
message: "✓ 监控内存使用"
not_found_message: "✗ 考虑添加内存使用监控"
optional: true
- type: create_file
conditions:
- pattern: "app\\.use\\(['\"]express-pino-logger['\"]|app\\.use\\(morgan\\(['\"]"
not_found: true
path: "src/middlewares/performance.middleware.js"
content: |
/**
* API性能监控中间件
*/
const responseTime = require('response-time');
const pino = require('pino');
const expressPino = require('express-pino-logger');
const os = require('os');
// 创建logger实例
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
}
}
});
// 创建express中间件
const requestLogger = expressPino({
logger,
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
query: req.query,
params: req.params,
headers: {
'user-agent': req.headers['user-agent'],
'content-type': req.headers['content-type'],
'authorization': req.headers.authorization ? '******' : undefined
}
}),
res: (res) => ({
statusCode: res.statusCode
}),
err: pino.stdSerializers.err
}
});
// 响应时间监控中间件
const responseTimeMiddleware = responseTime((req, res, time) => {
res.setHeader('X-Response-Time', `${time.toFixed(2)}ms`);
req.log.info({ responseTime: time });
// 可以在这里添加慢响应警告
if (time > 1000) {
req.log.warn({
message: '慢响应警告',
responseTime: time,
route: req.originalUrl
});
}
});
// 系统资源监控,定期记录
const startResourceMonitoring = (interval = 60000) => {
setInterval(() => {
const memoryUsage = process.memoryUsage();
const cpuUsage = os.loadavg();
logger.info({
type: 'system-metrics',
memory: {
rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB',
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB',
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
external: Math.round(memoryUsage.external / 1024 / 1024) + 'MB',
},
cpu: {
load1: cpuUsage[0],
load5: cpuUsage[1],
load15: cpuUsage[2]
},
uptime: process.uptime()
});
}, interval);
};
module.exports = {
requestLogger,
responseTimeMiddleware,
startResourceMonitoring,
logger
};
- type: suggest
message: |
在应用中集成性能监控中间件:
```javascript
// app.js
const express = require('express');
const {
requestLogger,
responseTimeMiddleware,
startResourceMonitoring
} = require('./middlewares/performance.middleware');
const app = express();
// 应用性能监控中间件(尽早使用)
app.use(requestLogger);
app.use(responseTimeMiddleware);
// 其他中间件和路由...
// 启动资源监控(服务器启动时)
startResourceMonitoring();
module.exports = app;
```
性能监控最佳实践:
1. **使用结构化日志**:使用像Pino这样的日志库,它专为性能而设计
2. **监控重要指标**:
- 响应时间
- 请求率
- 错误率
- 内存使用
- CPU负载
3. **设置阈值告警**:对慢响应和资源高使用率设置告警
4. **采集请求上下文**:记录每个请求的相关信息,方便调试
5. **使用APM工具**:考虑集成NewRelic、Datadog或Elastic APM等工具
6. **性能基准测试**:定期对API进行负载测试,确保性能稳定
metadata:
priority: medium
version: 1.0.0
tags: ["nodejs", "performance", "monitoring"]
</rule>
团队在项目中实施了这套规则后,取得了以下成效:
- API一致性提高 - 所有API端点遵循统一的RESTful设计规范,降低了前端集成难度
- 错误处理标准化 - 统一的错误处理机制使前端能够更可靠地处理异常情况
- 安全性改善 - 常见的安全漏洞被大幅减少,通过安全审计的比例显著提高
- 性能透明度增强 - 团队能够持续监控API性能并识别瓶颈
- 开发速度加快 - 标准化的模式和现成的模板使新API的开发速度提高了约30%
从这个案例中,团队总结了以下最佳实践:
- 以模板为起点 - 为常见API功能提供模板,减少从零开始的工作
- 文档和规则同步 - 将API文档生成集成到规则中,确保文档始终与代码同步
- 自动化安全检查 - 让规则自动检查常见安全问题,避免人为疏忽
- 性能监控内置 - 将性能监控作为标准实践内置到基础设施中
- 分层实施 - 先实施最关键的规则(如安全相关规则),然后逐步添加其他规则
团队还注意到一些需要注意的问题:
- 避免过度规范化 - 一些特殊API可能需要不同的设计模式,规则应该允许合理的例外
- 性能开销平衡 - 某些监控和安全措施可能带来性能开销,需要根据实际情况平衡
- 规则维护 - 随着框架和库的更新,规则也需要定期更新
在下一部分中,我们将探讨全栈开发场景中Cursor规则的应用案例,特别关注前后端协作和全栈应用的一致性问题。