使用 Clean Architecture 通过 Node.js、Express 和 TypeScript 进行现代 API 开发
API 是现代 Web 应用程序的支柱。随着应用程序复杂性的不断增长,采用能够提升可扩展性、可维护性和可测试性的架构至关重要。在本篇博文中,我们将探讨如何使用 Node.js、Express 和 TypeScript 构建现代 API,同时遵循清洁架构 (Clean Architecture) 原则。
请订阅我的YouTube 频道以支持我的频道并获取更多 Web 开发教程。
📑 目录
| 序号 | 部分 | 
|---|---|
| 1. | 🧩 清洁架构简介 | 
| 2. | 💡 为什么选择 Node.js、Express 和 TypeScript? | 
| 3. | 🚧 设置项目 | 
| 4. | 🏗️ 使用清晰的架构构建项目 | 
| 5. | 📂 实现领域层 | 
| 6. | 🔧 实现用例 | 
| 7. | 🗂️ 实现基础设施层 | 
| 8. | 🌐 实现界面层 | 
| 9. | 🔌依赖注入 | 
| 10. | 🚨错误处理 | 
| 11. | ✔️ 验证 | 
| 12. | 💾 真正的数据库集成 | 
| 13. | 🔒 身份验证和授权 | 
| 14. | 📝 日志记录和监控 | 
| 15. | ⚙️ 环境配置 | 
| 16. | 🚀 CI/CD 和部署 | 
| 17. | 🧹 代码质量和 Linting | 
| 18. | 🛠️ 项目文档 | 
| 19. | 🏁 结论 | 
1.🧩 清洁架构简介
清晰架构 (Clean Architecture) 由 Robert C. Martin(鲍勃大叔)提出,强调应用程序内部的关注点分离。它倡导业务逻辑应独立于任何框架、数据库或外部系统的理念。这使得应用程序更加模块化、更易于测试,并且能够适应变化。
清洁架构的关键原则:
- 独立性:核心业务逻辑不应依赖于外部库、UI、数据库或框架。
- 可测试性:应用程序应该易于测试,而无需依赖外部系统。
- 灵活性:应该可以轻松更改或替换应用程序的各个部分,而不会影响其他部分。
2. 💡 为什么选择 Node.js、Express 和 TypeScript?
Node.js
Node.js 是一个强大的 JavaScript 运行时,可用于构建可扩展的网络应用程序。它具有非阻塞和事件驱动的特性,非常适合构建处理大量请求的 API。
表达
Express 是一个极简的 Node.js Web 框架。它提供了一系列强大的功能,可用于构建 Web 和移动应用程序及 API。其简洁性使其易于上手,并且具有高度的可扩展性。
TypeScript
TypeScript 是 JavaScript 的超集,添加了静态类型。在 Node.js 应用程序中使用 TypeScript 有助于在开发过程中尽早捕获错误,提高代码可读性,并提升整体开发者体验。
3. 🚧 设置项目
首先,让我们创建一个新的 Node.js 项目并设置 TypeScript。
mkdir clean-architecture-api
cd clean-architecture-api
npm init -y
npm install express
npm install typescript @types/node @types/express ts-node-dev --save-dev
npx tsc --init
接下来,配置您的tsconfig.json:
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}
4. 🏗️ 使用清晰的架构构建项目
典型的 Clean Architecture 项目分为以下几层:
- 领域层:包含业务逻辑、实体和接口。此层独立于任何其他层。
- 用例层:包含应用程序的用例或业务规则。
- 基础设施层:包含领域层定义的接口的实现,例如数据库连接。
- 界面层:包含控制器、路由和任何其他与 Web 框架相关的代码。
目录结构可能如下所示:
src/
├── domain/
│   ├── entities/
│   └── interfaces/
├── use-cases/
├── infrastructure/
│   ├── database/
│   └── repositories/
└── interface/
    ├── controllers/
    └── routes/
5. 📂 实现领域层
在领域层,定义你的实体和接口。假设我们正在构建一个用于管理图书的简单 API。
实体(书籍):
// src/domain/entities/Book.ts
export class Book {
  constructor(
    public readonly id: string,
    public title: string,
    public author: string,
    public publishedDate: Date
  ) {}
}
存储库接口:
// src/domain/interfaces/BookRepository.ts
import { Book } from "../entities/Book";
export interface BookRepository {
  findAll(): Promise<Book[]>;
  findById(id: string): Promise<Book | null>;
  create(book: Book): Promise<Book>;
  update(book: Book): Promise<void>;
  delete(id: string): Promise<void>;
}
6. 🔧 实现用例
用例定义了系统中可执行的操作。它们与领域层交互,并且与所使用的框架或数据库无关。
用例(获取所有书籍):
// src/use-cases/GetAllBooks.ts
import { BookRepository } from "../domain/interfaces/BookRepository";
export class GetAllBooks {
  constructor(private bookRepository: BookRepository) {}
  async execute() {
    return await this.bookRepository.findAll();
  }
}
7.🗂️实现基础设施层
在基础架构层中,实现领域层中定义的接口。这是您与数据库或外部服务交互的地方。
内存存储库(为简单起见):
// src/infrastructure/repositories/InMemoryBookRepository.ts
import { Book } from "../../domain/entities/Book";
import { BookRepository } from "../../domain/interfaces/BookRepository";
export class InMemoryBookRepository implements BookRepository {
  private books: Book[] = [];
  async findAll(): Promise<Book[]> {
    return this.books;
  }
  async findById(id: string): Promise<Book | null> {
    return this.books.find(book => book.id === id) || null;
  }
  async create(book: Book): Promise<Book> {
    this.books.push(book);
    return book;
  }
  async update(book: Book): Promise<void> {
    const index = this.books.findIndex(b => b.id === book.id);
    if (index !== -1) {
      this.books[index] = book;
    }
  }
  async delete(id: string): Promise<void> {
    this.books = this.books.filter(book => book.id !== id);
  }
}
8.🌐实现界面层
接口层包含处理 HTTP 请求并将其映射到用例的控制器和路由。
图书控制器:
// src/interface/controllers/BookController.ts
import { Request, Response } from "express";
import { GetAllBooks } from "../../use-cases/GetAllBooks";
export class BookController {
  constructor(private getAllBooks: GetAllBooks) {}
  async getAll(req: Request, res: Response) {
    const books = await this.getAllBooks.execute();
    res.json(books);
  }
}
路线:
// src/interface/routes/bookRoutes.ts
import { Router } from "express";
import { InMemoryBookRepository } from "../../infrastructure/repositories/InMemoryBookRepository";
import { GetAllBooks }
 from "../../use-cases/GetAllBooks";
import { BookController } from "../controllers/BookController";
const router = Router();
const bookRepository = new InMemoryBookRepository();
const getAllBooks = new GetAllBooks(bookRepository);
const bookController = new BookController(getAllBooks);
router.get("/books", (req, res) => bookController.getAll(req, res));
export { router as bookRoutes };
主要用途:
// src/index.ts
import express from "express";
import { bookRoutes } from "./interface/routes/bookRoutes";
const app = express();
app.use(express.json());
app.use("/api", bookRoutes);
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
9. 🔌依赖注入
依赖注入 (DI) 是一种提供对象依赖项(而非在对象内部硬编码)的技术。它能够促进松耦合,并使您的应用程序更易于测试。
例子:
让我们使用 TypeScript 实现一个简单的 DI 机制。
// src/infrastructure/DIContainer.ts
import { InMemoryBookRepository } from "./repositories/InMemoryBookRepository";
import { GetAllBooks } from "../use-cases/GetAllBooks";
class DIContainer {
  private static _bookRepository = new InMemoryBookRepository();
  static getBookRepository() {
    return this._bookRepository;
  }
  static getGetAllBooksUseCase() {
    return new GetAllBooks(this.getBookRepository());
  }
}
export { DIContainer };
在你的控制器中使用 DIContainer:
// src/interface/controllers/BookController.ts
import { Request, Response } from "express";
import { DIContainer } from "../../infrastructure/DIContainer";
export class BookController {
  private getAllBooks = DIContainer.getGetAllBooksUseCase();
  async getAll(req: Request, res: Response) {
    const books = await this.getAllBooks.execute();
    res.json(books);
  }
}
10. 🚨错误处理
适当的错误处理可确保您的 API 能够正常处理意外情况并向客户端提供有意义的错误消息。
例子:
创建一个集中的错误处理中间件:
// src/interface/middleware/errorHandler.ts
import { Request, Response, NextFunction } from "express";
export function errorHandler(err: any, req: Request, res: Response, next: NextFunction) {
  console.error(err.stack);
  res.status(500).json({ message: "Internal Server Error" });
}
在您的主应用程序中使用此中间件:
// src/index.ts
import express from "express";
import { bookRoutes } from "./interface/routes/bookRoutes";
import { errorHandler } from "./interface/middleware/errorHandler";
const app = express();
app.use(express.json());
app.use("/api", bookRoutes);
app.use(errorHandler);
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
11.✔️验证
验证对于确保输入应用程序的数据正确且安全至关重要。
例子:
集成class-validator以验证传入的请求:
npm install class-validator class-transformer
为书籍创作创建一个 DTO(数据传输对象):
// src/interface/dto/CreateBookDto.ts
import { IsString, IsDate } from "class-validator";
export class CreateBookDto {
  @IsString()
  title!: string;
  @IsString()
  author!: string;
  @IsDate()
  publishedDate!: Date;
}
验证控制器中的 DTO:
// src/interface/controllers/BookController.ts
import { Request, Response } from "express";
import { validate } from "class-validator";
import { CreateBookDto } from "../dto/CreateBookDto";
import { DIContainer } from "../../infrastructure/DIContainer";
export class BookController {
  private getAllBooks = DIContainer.getGetAllBooksUseCase();
  async create(req: Request, res: Response) {
    const dto = Object.assign(new CreateBookDto(), req.body);
    const errors = await validate(dto);
    if (errors.length > 0) {
      return res.status(400).json({ errors });
    }
    // Proceed with the creation logic...
  }
}
12. 💾 真正的数据库集成
从内存数据库切换到 MongoDB 或 PostgreSQL 等真实数据库可以让您的应用程序投入生产。
例子:
集成 MongoDB:
npm install mongoose @types/mongoose
为以下项创建 Mongoose 模型Book:
// src/infrastructure/models/BookModel.ts
import mongoose, { Schema, Document } from "mongoose";
interface IBook extends Document {
  title: string;
  author: string;
  publishedDate: Date;
}
const BookSchema: Schema = new Schema({
  title: { type: String, required: true },
  author: { type: String, required: true },
  publishedDate: { type: Date, required: true },
});
const BookModel = mongoose.model<IBook>("Book", BookSchema);
export { BookModel, IBook };
实施存储库:
// src/infrastructure/repositories/MongoBookRepository.ts
import { Book } from "../../domain/entities/Book";
import { BookRepository } from "../../domain/interfaces/BookRepository";
import { BookModel } from "../models/BookModel";
export class MongoBookRepository implements BookRepository {
  async findAll(): Promise<Book[]> {
    return await BookModel.find();
  }
  async findById(id: string): Promise<Book | null> {
    return await BookModel.findById(id);
  }
  async create(book: Book): Promise<Book> {
    const newBook = new BookModel(book);
    await newBook.save();
    return newBook;
  }
  async update(book: Book): Promise<void> {
    await BookModel.findByIdAndUpdate(book.id, book);
  }
  async delete(id: string): Promise<void> {
    await BookModel.findByIdAndDelete(id);
  }
}
更新 DIContainer 以使用 MongoBookRepository:
// src/infrastructure/DIContainer.ts
import { MongoBookRepository } from "./repositories/MongoBookRepository";
import { GetAllBooks } from "../use-cases/GetAllBooks";
class DIContainer {
  private static _bookRepository = new MongoBookRepository();
  static getBookRepository() {
    return this._bookRepository;
  }
  static getGetAllBooksUseCase() {
    return new GetAllBooks(this.getBookRepository());
  }
}
export { DIContainer };
13. 🔒 身份验证和授权
保护您的 API 至关重要。JWT(JSON Web 令牌)是一种常见的无状态身份验证方法。
例子:
集成JWT进行身份验证:
npm install jsonwebtoken @types/jsonwebtoken
创建身份验证中间件:
// src/interface/middleware/auth.ts
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";
export function authenticateToken(req: Request, res: Response, next: NextFunction) {
  const token = req.header("Authorization")?.split(" ")[1];
  if (!token) return res.sendStatus(401);
  jwt.verify(token, process.env.JWT_SECRET as string, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}
使用此中间件来保护路由:
// src/interface/routes/bookRoutes.ts
import { Router } from "express";
import { BookController } from "../controllers/BookController";
import { authenticateToken } from "../middleware/auth";
const router = Router();
router.get("/books", authenticateToken, (req, res) => bookController.getAll(req, res));
export { router as bookRoutes };
14. 📝 日志记录和监控
日志记录对于在生产中调试和监控应用程序至关重要。
例子:
集成日志记录winston:
npm install winston
创建记录器:
// src/infrastructure/logger.ts
import { createLogger, transports, format } from "winston";
const logger = createLogger({
  level: "info",
  format: format.combine(format.timestamp(), format.json()),
  transports: [new transports.Console()],
});
export { logger };
在您的应用程序中使用记录器:
// src/index.ts
import express from "express";
import { bookRoutes } from "./interface/routes/bookRoutes";
import { errorHandler } from "./interface/middleware/errorHandler";
import { logger } from "./infrastructure/logger";
const app = express();
app.use(express.json());
app.use("/api", bookRoutes);
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  logger.info(`Server is running on port ${PORT}`);
});
15. ⚙️ 环境配置
管理不同的环境对于确保您的应用程序在开发、测试和生产中正确运行至关重要。
例子:
使用`
dotenv` 用于环境配置:
npm install dotenv
创建.env文件:
PORT=3000
JWT_SECRET=your_jwt_secret
在您的应用程序中加载环境变量:
// src/index.ts
import express from "express";
import dotenv from "dotenv";
dotenv.config();
import { bookRoutes } from "./interface/routes/bookRoutes";
import { errorHandler } from "./interface/middleware/errorHandler";
import { logger } from "./infrastructure/logger";
const app = express();
app.use(express.json());
app.use("/api", bookRoutes);
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  logger.info(`Server is running on port ${PORT}`);
});
16.🚀 CI/CD 和部署
自动化 API 的测试、构建和部署可确保一致性和可靠性。
例子:
为 CI/CD 设置 GitHub Actions:
创建.github/workflows/ci.yml文件:
name: Node.js CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.x, 16.x]
    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install
    - run: npm test
17.🧹代码质量和 Linting
在协作环境中保持一致的代码质量至关重要。
例子:
集成 ESLint 和 Prettier:
npm install eslint prettier eslint-config-prettier eslint-plugin-prettier --save-dev
创建 ESLint 配置:
// .eslintrc.json
{
  "env": {
    "node": true,
    "es6": true
  },
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
  "plugins": ["@typescript-eslint", "prettier"],
  "parser": "@typescript-eslint/parser",
  "rules": {
    "prettier/prettier": "error"
  }
}
添加 Prettier 配置:
// .prettierrc
{
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 80
}
18.🛠️项目文档
记录您的 API 对于开发人员和最终用户来说都至关重要。
例子:
使用 Swagger 生成 API 文档:
npm install swagger-jsdoc swagger-ui-express
创建 Swagger 文档:
// src/interface/swagger.ts
import swaggerJSDoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import { Express } from "express";
const options = {
  definition: {
    openapi: "3.0.0",
    info: {
      title: "Clean Architecture API",
      version: "1.0.0",
    },
  },
  apis: ["./src/interface/routes/*.ts"],
};
const swaggerSpec = swaggerJSDoc(options);
function setupSwagger(app: Express) {
  app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
}
export { setupSwagger };
在您的主应用程序中设置 Swagger:
// src/index.ts
import express from "express";
import dotenv from "dotenv";
dotenv.config();
import { bookRoutes } from "./interface/routes/bookRoutes";
import { errorHandler } from "./interface/middleware/errorHandler";
import { logger } from "./infrastructure/logger";
import { setupSwagger } from "./interface/swagger";
const app = express();
app.use(express.json());
app.use("/api", bookRoutes);
app.use(errorHandler);
setupSwagger(app);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  logger.info(`Server is running on port ${PORT}`);
});
19. 🏁 结论
在本篇博文中,我们探讨了如何在遵循“清晰架构”原则的情况下,使用 Node.js、Express 和 TypeScript 构建现代 API。我们在初始实现的基础上进行了扩展,添加了依赖注入、错误处理、验证、真实数据库集成、身份验证和授权、日志记录和监控、环境配置、CI/CD、代码质量和 Linting 以及项目文档等关键功能。
通过遵循这些实践,您将确保您的 API 不仅功能齐全,而且易于维护、可扩展且可用于生产环境。随着您继续开发,您可以随意探索其他模式和工具,以进一步增强您的应用程序。
开始你的 JavaScript 之旅
如果您是 JavaScript 新手或想要复习一下,请访问我的 BuyMeACoffee 博客来了解基础知识。
系列索引
| 部分 | 标题 | 关联 | 
|---|---|---|
| 1 | 告别密码:使用 FACEIO 为您的网站添加面部识别功能 | 读 | 
| 2 | 终极 Git 命令速查表 | 读 | 
| 3 | 学习和掌握 JavaScript 的 12 个最佳资源 | 读 | 
| 4 | Angular 与 React:全面比较 | 读 | 
| 5 | 编写干净代码的十大 JavaScript 最佳实践 | 读 | 
| 6 | 面向所有开发人员的 20 大 JavaScript 技巧和提示 | 读 | 
| 7 | 你需要了解的 8 个令人兴奋的 JavaScript 新概念 | 读 | 
| 8 | JavaScript 应用程序状态管理的 7 大技巧 | 读 | 
| 9 | 🔒 基本 Node.js 安全最佳实践 | 读 | 
| 10 | 优化 Angular 性能的 10 个最佳实践 | 读 | 
| 11 | 十大 React 性能优化技术 | 读 | 
| 12 | 提升你的作品集的 15 个最佳 JavaScript 项目 | 读 | 
| 十三 | 掌握 Node.js 的 6 个存储库 | 读 | 
| 14 | 掌握 Next.js 的 6 个最佳存储库 | 读 | 
| 15 | 用于构建交互式 UI 的 5 大 JavaScript 库 | 读 | 
| 16 | 每个开发人员都应该知道的 3 大 JavaScript 概念 | 读 | 
| 17 | 20 种提升 Node.js 性能的方法 | 读 | 
| 18 | 使用压缩中间件提升 Node.js 应用性能 | 读 | 
| 19 | 理解 Dijkstra 算法:分步指南 | 读 | 
| 20 | 了解 NPM 和 NVM:Node.js 开发的基本工具 | 读 | 
关注并订阅:
- YouTube:与 Dipak 一起进行 devDive
- 网站:Dipak Ahirav
- Whatsapp 频道:DevDiveWithDipak
- 电子邮件: dipaksahirav@gmail.com
- LinkedIn : Dipak Ahirav
 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com