在 Node.js 生态快速发展的今天,如何搭建一个既健壮又易于维护的现代 Node.js 项目?本文将带你从零开始,深入探讨现代 Node.js 项目的架构设计、工具链配置和最佳实践,帮助你构建出符合生产标准的应用程序。
为什么需要现代化的项目结构?
随着项目规模的增长,一个良好的项目结构能够:
- 提高代码可维护性:清晰的目录结构让团队成员快速理解代码组织
- 增强可测试性:模块化设计便于单元测试和集成测试
- 便于团队协作:统一的代码规范和工具链减少协作成本
- 支持持续集成/部署:标准化的配置简化 DevOps 流程
项目初始化与基础配置
1. 项目初始化
# 创建项目目录
mkdir modern-node-app
cd modern-node-app
# 初始化 package.json
npm init -y
# 使用 TypeScript(推荐)
npm install typescript @types/node --save-dev
npx tsc --init
2. 基础目录结构
modern-node-app/
├── src/
│ ├── core/ # 核心业务逻辑
│ ├── modules/ # 功能模块
│ ├── shared/ # 共享代码
│ ├── config/ # 配置文件
│ ├── middleware/ # 中间件
│ └── app.ts # 应用入口
├── tests/ # 测试文件
├── scripts/ # 构建/部署脚本
├── docs/ # 文档
├── .env.example # 环境变量示例
├── .gitignore
├── package.json
└── tsconfig.json
核心架构设计
分层架构模式
现代 Node.js 项目通常采用分层架构,确保关注点分离:
// src/core/application/Application.ts
export abstract class Application {
protected abstract setupMiddleware(): void;
protected abstract setupRoutes(): void;
protected abstract setupErrorHandling(): void;
public async initialize(): Promise<void> {
await this.setupDatabase();
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
}
protected async setupDatabase(): Promise<void> {
// 数据库连接逻辑
}
}
依赖注入容器
使用依赖注入提高代码的可测试性和可维护性:
// src/core/di/Container.ts
export class Container {
private services = new Map<string, any>();
register<T>(key: string, factory: () => T): void {
this.services.set(key, factory);
}
resolve<T>(key: string): T {
const factory = this.services.get(key);
if (!factory) {
throw new Error(`Service ${key} not found`);
}
return factory();
}
}
// 使用示例
const container = new Container();
container.register('userService', () => new UserService());
const userService = container.resolve<UserService>('userService');
现代化工具链配置
1. TypeScript 配置优化
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@core/*": ["src/core/*"],
"@modules/*": ["src/modules/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
2. ESLint + Prettier 代码规范
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', {
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_'
}]
},
env: {
node: true,
es2022: true
}
};
3. 热重载开发体验
// package.json scripts 部分
{
"scripts": {
"dev": "nodemon --watch src --ext ts,json --exec \"ts-node src/app.ts\"",
"build": "tsc",
"start": "node dist/app.js",
"test": "jest",
"lint": "eslint src/**/*.ts",
"format": "prettier --write src/**/*.ts",
"type-check": "tsc --noEmit"
}
}
模块化设计实践
用户模块示例
// src/modules/users/user.module.ts
export class UserModule {
constructor(
private readonly userService: UserService,
private readonly userController: UserController,
private readonly userRepository: UserRepository
) {}
async initialize(): Promise<void> {
// 模块初始化逻辑
}
}
// src/modules/users/user.service.ts
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
async createUser(userData: CreateUserDto): Promise<User> {
// 业务逻辑验证
if (await this.userRepository.existsByEmail(userData.email)) {
throw new Error('Email already exists');
}
// 密码加密
const hashedPassword = await this.hashPassword(userData.password);
// 创建用户
const user = await this.userRepository.create({
...userData,
password: hashedPassword
});
// 发送欢迎邮件(异步)
this.sendWelcomeEmail(user.email).catch(console.error);
return user;
}
private async hashPassword(password: string): Promise<string> {
// 使用 bcrypt 等库进行密码哈希
return password; // 简化示例
}
private async sendWelcomeEmail(email: string): Promise<void> {
// 邮件发送逻辑
}
}
错误处理与日志记录
统一的错误处理中间件
// src/middleware/error.middleware.ts
import { Request, Response, NextFunction } from 'express';
export class ErrorMiddleware {
static handle(
error: Error,
req: Request,
res: Response,
next: NextFunction
): void {
// 记录错误日志
console.error(`[${new Date().toISOString()}] Error:`, {
message: error.message,
stack: error.stack,
path: req.path,
method: req.method,
ip: req.ip
});
// 根据错误类型返回不同的状态码
const statusCode = this.getStatusCode(error);
const response = this.formatErrorResponse(error, statusCode);
res.status(statusCode).json(response);
}
private static getStatusCode(error: Error): number {
// 根据错误类型返回相应的 HTTP 状态码
if (error.name === 'ValidationError') return 400;
if (error.name === 'UnauthorizedError') return 401;
if (error.name === 'NotFoundError') return 404;
return 500;
}
private static formatErrorResponse(
error: Error,
statusCode: number
): object {
const response: any = {
success: false,
error: error.message,
timestamp: new Date().toISOString()
};
// 开发环境返回堆栈信息
if (process.env.NODE_ENV === 'development') {
response.stack = error.stack;
}
return response;
}
}
结构化日志记录
// src/core/logging/Logger.ts
import winston from 'winston';
export class Logger {
private static instance: winston.Logger;
static getInstance(): winston.Logger {
if (!Logger.instance) {
Logger.instance = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack