Node.js 后端构建可扩展应用程序:项目结构实用指南

对于初级开发人员来说,构建 Node.js 后端最具挑战性的方面之一不是编写代码本身,而是以可扩展的方式组织代码。今天,我们将探索一个可用于生产的 Node.js 项目结构,您可以将其用作应用程序的模板。

非结构化代码的问题

在我们深入探讨之前,想象一下在图书馆中寻找一本特定的书,而图书馆的书籍都是随机摆放在书架上的。很令人沮丧,对吧?代码也是如此。如果没有适当的结构,您的 Node.js 应用程序很快就会变成难以维护和扩展的意大利面条式代码迷宫。

更好的方法:现代 Node.js 项目结构

让我们分解一下许多成功公司使用的专业级 Node.js 项目结构:

📁 后端/

═─📁 src/

│ └── 📁 @types # TypeScript 类型定义

│ └──📁 config # 配置文件

│ └── 📁 controllers # 请求处理程序

│ └── 📁 entity # 数据库模型/实体

│ └── 📁 helper # 辅助/实用函数

│ └── 📁 中间件 # Express 中间件

│ └── 📁 routes # API 路由定义

│ └── 📁 services # 业务逻辑

│ └── 📁 types # 附加类型定义

│ └── 📁 utils # 实用函数

└── 📄 app.ts # 应用程序入口点

└── 📄 .eslintrc.js # ESLint 配置

└── 📄 .prettierrc # Prettier 配置

└── 📄 Dockerfile # Docker 配置

└── 📄 package.json # 项目依赖项

└── 📄 tsconfig.json # TypeScript 配置

└── 📄 .dockerignore # Docker 忽略规则

└── 📄 .env # 环境变量

└── 📄 docker-compose.yml # Docker Compose 配置

了解每个组件

**1. @types 和 types 目录**

`// @types/express/index.d.ts
declare namespace Express {
 export interface Request {
 user?: {
 id: string;
 role: string;
 };
 }
}`

这些文件夹包含 TypeScript 类型定义。@types 文件夹通常包含外部模块的声明,而 types 则包含特定于应用程序的类型。

**2. 配置目录**

// config/database.ts
export const dbConfig = {
 host: process.env.DB_HOST,
 port: process.env.DB_PORT,
 username: process.env.DB_USER,
 // … other configuration
};

该目录包含所有配置文件,便于管理不同的环境(开发、暂存、生产)。

**3. 控制器**

// controllers/userController.ts
export class UserController {
  async getUser(req: Request, res: Response) {
    try {
      const user = await userService.findById(req.params.id);
      res.json(user);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }
}

控制器处理 HTTP 请求和响应,充当路由和服务之间的桥梁。

**4. 实体**

typescript// entity/User.ts
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  email: string;
}

实体目录包含您的数据库模型,通常使用 TypeORM 或 Sequelize 等 ORM。

**5. 服务**

services/userService.ts
export class UserService {
  async createUser(userData: CreateUserDto) {
    const user = new User();
    Object.assign(user, userData);
    return await this.userRepository.save(user);
  }
}

服务包含您的业务逻辑,并将其与您的控制器分开。

**6. 中间件**

// middlewares/auth.ts
export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) throw new Error('No token provided');
    // Verify token and set user
    next();
  } catch (error) {
    res.status(401).json({ error: 'Unauthorized' });
  }
};

中间件处理诸如身份验证、日志记录和错误处理等跨切关注点。

**最佳实践和技巧**

**1. 单一职责:**每个目录应具有明确、单一的用途。不要将业务逻辑与路由定义混为一谈。

**2. 依赖注入:**使用依赖注入让你的代码更易于测试和维护。

// Better approach
class UserService {
  constructor(private userRepository: UserRepository) {}
}

**3. 环境配置:**使用 .env 文件来配置特定于环境的变量,并且永远不要将它们提交到版本控制中。

**4. Docker 集成:**Dockerfile 和 docker-compose.yml 的存在表明支持容器化,使得跨环境的部署保持一致。

**应避免的常见陷阱**

循环依赖:注意不要在模块之间创建循环依赖。

海量文件:如果文件变得太大,则可能是执行了太多操作。将其拆分为更小、更集中的模块。

不一致的错误处理:在您的应用程序中建立一致的错误处理策略。

**结论**

结构良好的 Node.js 应用程序对于长期的可维护性和可扩展性至关重要。此结构提供了坚实的基础,您可以在应用程序增长时在此基础上进行构建。请记住,目标不仅仅是让它工作 - 而是让它可维护、可扩展并且使用起来令人愉快。

下次启动新的 Node.js 项目时,请考虑使用此结构作为模板。它将为您节省无数重构时间,并使您的代码库从第一天起就更加专业。

“专业提示:创建具有此结构的模板存储库,以便您可以快速使用同一组织启动新项目。”