详细分析 ▾
运行时依赖
版本
初始发布 — 生产级 Node.js 后端模式。包括 Express 和 Fastify 设置、分层架构、错误处理、验证、JWT 身份验证、数据库集成、安全和可维护性指南。
安装命令
点击复制技能文档
使用 TypeScript 构建可扩展、可维护的 Node.js 后端应用程序的模式。
永远不要
- 永远不要在代码中存储密钥 - 使用环境变量,永远不要硬编码凭证
- 永远不要跳过输入验证 - 使用 Zod/Joi 在中间件层验证所有输入
- 永远不要在生产环境中暴露错误详情 - 返回通用消息,在服务器端记录详情
- 永远不要使用
any类型 - TypeScript 类型防止运行时错误 - 永远不要跳过错误处理 - 始终包装异步处理器,使用全局错误中间件
- 永远不要使用同步操作 - 对 I/O 使用 async/await,切勿在处理器中使用
fs.readFileSync - 永远不要信任客户端输入 - 清理、验证并参数化所有查询
何时使用
- 使用 Express 或 Fastify 构建 REST API
- 设置中间件管道和错误处理
- 实现身份验证和授权
- 使用连接池和事务集成数据库
- 添加验证、缓存和速率限制
项目结构 — 分层架构
src/
├── controllers/ # 处理 HTTP 请求/响应
├── services/ # 业务逻辑
├── repositories/ # 数据访问层
├── models/ # 数据模型和类型
├── middleware/ # 认证、验证、日志、错误
├── routes/ # 路由定义
├── config/ # 数据库、缓存、环境配置
└── utils/ # 辅助工具、自定义错误、响应格式化
控制器处理 HTTP 事务,服务包含业务逻辑,仓储抽象数据访问。每一层只能调用其下一层。
Express 设置
import express from "express"; import helmet from "helmet"; import cors from "cors"; import compression from "compression";const app = express();
app.use(helmet()); app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") })); app.use(compression()); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" }));
Fastify 设置
import Fastify from "fastify"; import helmet from "@fastify/helmet"; import cors from "@fastify/cors";const fastify = Fastify({ logger: { level: process.env.LOG_LEVEL || "info" }, });
await fastify.register(helmet); // ... 其他插件
中间件
// 验证中间件 const validateRequest = (schema: ZodSchema) => { return (req: Request, res: Response, next: NextFunction) => { try { schema.parse(req.body); next(); } catch (error) { res.status(400).json({ error: "Validation failed", details: error }); } }; };// 速率限制 import rateLimit from "express-rate-limit";
const limiter = rateLimit({ windowMs: 15 60 1000, // 15 分钟 max: 100, // 每个 IP 限制 100 个请求 });
app.use("/api/", limiter);
// 认证中间件 const authenticate = (req: Request, res: Response, next: NextFunction) => { 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 as any).user = decoded; next(); } catch { res.status(401).json({ error: "Invalid token" }); } };
错误处理
// 自定义错误类 class AppError extends Error { constructor( public statusCode: number, public message: string, public isOperational = true ) { super(message); } }
// 全局错误处理器 app.use((err: Error, req: Request, res: Response, next: NextFunction) => { if (err instanceof AppError) { return res.status(err.statusCode).json({ error: err.message, }); } // 生产环境不暴露详情 console.error("Server error:", err); res.status(500).json({ error: process.env.NODE_ENV === "production" ? "Internal server error" : err.message, }); });
数据验证
import { z } from "zod";// 创建验证 schema const createUserSchema = z.object({ email: z.string().email(), password: z.string().min(8), name: z.string().min(2).optional(), });
const updateUserSchema = createUserSchema.partial();
// 在控制器中使用 const createUser = async (req: Request, res: Response) => { const data = createUserSchema.parse(req.body); // ... 创建用户 };
数据库集成
// 仓储模式
class UserRepository {
constructor(private db: Pool) {}
async findById(id: string): Promise {
const result = await this.db.query(
"SELECT FROM users WHERE id = $1",
[id]
);
return result.rows[0] || null;
}
async create(data: CreateUserData): Promise {
const result = await this.db.query(
INSERT INTO users (email, password_hash, name)
VALUES ($1, $2, $3) RETURNING ,
[data.email, data.passwordHash, data.name]
);
return result.rows[0];
}
}
身份验证
import jwt from "jsonwebtoken"; import bcrypt from "bcrypt";// JWT 签名 const signToken = (userId: string): string => { return jwt.sign( { userId }, process.env.JWT_SECRET!, { expiresIn: "7d" } ); };
// 密码哈希 const hashPassword = async (password: string): Promise => { return bcrypt.hash(password, 12); };
// 密码验证 const verifyPassword = async ( password: string, hash: string ): Promise => { return bcrypt.compare(password, hash); };
缓存
import Redis from "ioredis";const redis = new Redis(process.env.REDIS_URL);
// 缓存中间件 const cacheMiddleware = (key: string, ttl: number = 300) => { return async (req: Request, res: Response, next: NextFunction) => { const cached = await redis.get(key); if (cached) { return res.json(JSON.parse(cached)); } // 拦截响应 const originalJson = res.json.bind(res); res.json = (data: any) => { redis.setex(key, ttl, JSON.stringify(data)); return originalJson(data); }; next(); }; };
速率限制
// 滑动窗口速率限制
const slidingWindowLimiter = async (
key: string,
limit: number,
window: number
): Promise => {
const now = Date.now();
const windowStart = now - window;
// 移除旧请求
await redis.zremrangebyscore(key, 0, windowStart);
// 计算当前请求数
const count = await redis.zcard(key);
if (count >= limit) {
return false;
}
// 添加新请求
await redis.zadd(key, now, ${now}-${Math.random()});
await redis.expire(key, Math.ceil(window / 1000));
return true;
};
配置管理
import dotenv from "dotenv";dotenv.config();
export const config = { port: parseInt(process.env.PORT || "3000", 10), nodeEnv: process.env.NODE_ENV || "development", database: { host: process.env.DB_HOST!, port: parseInt(process.env.DB_PORT || "5432", 10), name: process.env.DB_NAME!, user: process.env.DB_USER!, password: process.env.DB_PASSWORD!, }, redis: { url: process.env.REDIS_URL!, }, jwt: { secret: process.env.JWT_SECRET!, expiresIn: process.env.JWT_EXPIRES_IN || "7d", }, };
日志
import pino from "pino";const logger = pino({ level: process.env.LOG_LEVEL || "info", transport: process.env.NODE_ENV !== "production" ? { target: "pino-pretty" } : undefined, });
// 使用 logger.info({ userId: req.user?.id }, "Request received"); logger.error({ err, userId: req.user?.id }, "Request failed");
最佳实践
- 分层架构 - 控制器→服务→仓储→数据库
- 验证 - 在中间件层验证所有输入
- 错误处理 - 使用全局错误处理器和自定义错误类
- 安全 - 使用 helmet、cors、速率限制
- 缓存 - Redis 缓存频繁访问的数据
- 类型 - 始终使用 TypeScript 类型,避免
any - 配置 - 环境变量管理所有配置
- 日志 - 结构化日志便于调试