使用 TypeScript 的 TypeORM 构建健壮的 Node.js API

使用 TypeScript 的 TypeORM 构建健壮的 Node.js API

引言

大家好,欢迎来到今天的讲座!今天我们要一起探讨如何使用 TypeScript 和 TypeORM 构建一个健壮的 Node.js API。如果你是第一次接触这些技术,别担心,我会尽量用轻松诙谐的语言,让你在愉快的氛围中掌握这些知识。如果你已经有一定的经验,那么这篇文章也会为你提供一些新的见解和技巧。准备好了吗?那我们就开始吧!

什么是 Node.js?

首先,让我们快速回顾一下 Node.js。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它允许你在服务器端运行 JavaScript 代码。Node.js 的最大特点是它的异步 I/O 模型,这使得它非常适合处理高并发的网络应用。你可以用 Node.js 构建各种类型的后端服务,从简单的 REST API 到复杂的实时应用。

为什么选择 TypeScript?

TypeScript 是 JavaScript 的超集,它为 JavaScript 添加了静态类型检查。这意味着你可以在编写代码时就发现潜在的错误,而不是等到运行时才发现问题。TypeScript 还提供了更好的工具支持,比如智能提示、自动补全等功能,大大提高了开发效率。对于大型项目来说,TypeScript 可以帮助你更好地组织代码,避免不必要的错误。

什么是 TypeORM?

TypeORM 是一个面向对象的 ORM(对象关系映射)库,它可以帮助你在 Node.js 应用中与数据库进行交互。TypeORM 支持多种数据库,包括 MySQL、PostgreSQL、SQLite、Oracle 等。它最大的优点是能够将数据库表映射为 TypeScript 类,这样你就可以像操作普通对象一样操作数据库记录,而不需要写繁琐的 SQL 查询语句。TypeORM 还提供了丰富的功能,比如迁移、事务、连接池等,帮助你构建健壮的数据库层。

为什么要使用 TypeORM?

  1. 简化数据库操作:TypeORM 让你可以通过实体类来操作数据库,而不需要直接编写 SQL 语句。
  2. 支持多种数据库:TypeORM 支持多种主流数据库,你可以根据项目需求灵活选择。
  3. 自动迁移:TypeORM 提供了强大的迁移功能,可以自动生成数据库表结构,减少手动操作。
  4. 事务支持:TypeORM 支持事务操作,确保数据的一致性和完整性。
  5. 性能优化:TypeORM 内置了连接池机制,可以有效提高数据库访问的性能。

环境搭建

在开始编写代码之前,我们需要先搭建好开发环境。这里我们假设你已经安装了 Node.js 和 npm(Node.js 的包管理工具)。如果你还没有安装,建议先去官网下载并安装最新版本。

1. 创建项目目录

首先,创建一个新的项目目录,并进入该目录:

mkdir nodejs-api
cd nodejs-api

2. 初始化项目

接下来,使用 npm init 命令初始化项目,并按照提示输入项目信息。如果你想跳过所有提示,可以直接使用 -y 参数:

npm init -y

这会在当前目录下生成一个 package.json 文件,里面包含了项目的依赖和配置信息。

3. 安装依赖

现在,我们需要安装一些必要的依赖包。首先是 typescriptts-node,它们分别是 TypeScript 编译器和用于在 Node.js 中运行 TypeScript 代码的工具。然后是 typeormreflect-metadata,前者是我们要用的 ORM 库,后者是 TypeORM 所需的反射元数据库。最后,我们还需要安装一个具体的数据库驱动,比如 pg(PostgreSQL 驱动)或 mysql2(MySQL 驱动)。

npm install typescript ts-node typeorm reflect-metadata pg --save

4. 配置 TypeScript

为了让 TypeScript 正常工作,我们需要创建一个 tsconfig.json 文件。这个文件定义了 TypeScript 编译器的行为。你可以在项目根目录下创建一个 tsconfig.json 文件,并添加以下内容:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

5. 配置 TypeORM

TypeORM 需要一个配置文件来告诉它如何连接到数据库。你可以在项目根目录下创建一个 ormconfig.json 文件,并添加以下内容:

{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "username": "your_username",
  "password": "your_password",
  "database": "your_database",
  "synchronize": true,
  "logging": false,
  "entities": ["src/entity/**/*.ts"],
  "migrations": ["src/migration/**/*.ts"],
  "subscribers": ["src/subscriber/**/*.ts"]
}

注意:synchronize 选项设置为 true 会自动同步数据库表结构,这对于开发阶段很方便,但在生产环境中应该禁用此选项,以防止意外的数据丢失。

6. 创建项目结构

为了让项目更加整洁,我们可以创建一个基本的项目结构。在项目根目录下创建以下文件夹:

.
├── src
│   ├── entity
│   ├── migration
│   ├── subscriber
│   └── app.ts
└── test
  • src/entity:存放数据库实体类。
  • src/migration:存放数据库迁移文件。
  • src/subscriber:存放事件订阅者。
  • src/app.ts:主应用程序入口文件。
  • test:存放测试文件。

创建第一个实体

现在我们已经搭建好了开发环境,接下来让我们创建一个简单的实体类。实体类是 TypeORM 中的核心概念,它代表了数据库中的表。每个实体类对应一张表,实体类的属性对应表中的字段。

1. 创建用户实体

src/entity 文件夹下创建一个 User.ts 文件,并添加以下代码:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 50 })
  name: string;

  @Column({ length: 100, unique: true })
  email: string;

  @Column({ length: 60 })
  password: string;

  @Column({ default: true })
  isActive: boolean;
}

这段代码定义了一个 User 实体类,它对应数据库中的 users 表。@Entity() 装饰器表示这是一个实体类,@PrimaryGeneratedColumn() 表示 id 字段是主键并且自增,@Column() 表示普通字段。你可以根据需要添加更多的字段和约束。

2. 同步数据库

TypeORM 提供了 synchronize 选项,可以自动同步数据库表结构。我们在 ormconfig.json 中已经启用了这个选项,所以只需要运行以下命令,TypeORM 就会自动创建 users 表:

npx typeorm schema:sync

如果你不想使用 synchronize 选项,可以使用迁移功能来手动管理数据库表结构。我们稍后会详细介绍迁移的使用方法。

创建 API 服务器

接下来,我们要创建一个简单的 API 服务器,用于处理 HTTP 请求。我们将使用 Express.js 作为 Web 框架,并结合 TypeORM 来实现 CRUD 操作。

1. 安装 Express

首先,安装 Express 及其 TypeScript 类型定义:

npm install express @types/express --save

2. 创建 Express 服务器

src/app.ts 文件中,添加以下代码:

import 'reflect-metadata';
import express from 'express';
import { createConnection } from 'typeorm';
import { User } from './entity/User';

const app = express();
app.use(express.json());

// 连接数据库
createConnection().then(async connection => {
  console.log('Connected to the database!');

  // 获取用户仓库
  const userRepository = connection.getRepository(User);

  // 创建用户
  app.post('/users', async (req, res) => {
    const { name, email, password } = req.body;
    const user = new User();
    user.name = name;
    user.email = email;
    user.password = password;
    await userRepository.save(user);
    res.status(201).send(user);
  });

  // 获取所有用户
  app.get('/users', async (req, res) => {
    const users = await userRepository.find();
    res.send(users);
  });

  // 获取单个用户
  app.get('/users/:id', async (req, res) => {
    const user = await userRepository.findOne(req.params.id);
    if (!user) return res.status(404).send('User not found');
    res.send(user);
  });

  // 更新用户
  app.put('/users/:id', async (req, res) => {
    const { name, email, password, isActive } = req.body;
    const user = await userRepository.findOne(req.params.id);
    if (!user) return res.status(404).send('User not found');
    user.name = name || user.name;
    user.email = email || user.email;
    user.password = password || user.password;
    user.isActive = isActive !== undefined ? isActive : user.isActive;
    await userRepository.save(user);
    res.send(user);
  });

  // 删除用户
  app.delete('/users/:id', async (req, res) => {
    const result = await userRepository.delete(req.params.id);
    if (result.affected === 0) return res.status(404).send('User not found');
    res.status(204).send();
  });

  // 启动服务器
  const PORT = process.env.PORT || 3000;
  app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
  });
}).catch(error => console.log('Error connecting to the database:', error));

这段代码创建了一个 Express 服务器,并定义了五个路由来处理用户的 CRUD 操作。每个路由都使用了 TypeORM 的 getRepository 方法来获取用户仓库,并通过 savefindfindOnedelete 等方法来操作数据库。

3. 测试 API

现在你可以启动服务器并测试 API。首先,确保 PostgreSQL 数据库已经启动并运行,然后在终端中运行以下命令:

npx ts-node src/app.ts

服务器启动后,你可以使用 Postman 或其他 API 测试工具来发送请求。例如,你可以发送一个 POST 请求到 /users 来创建新用户,发送一个 GET 请求到 /users 来获取所有用户,等等。

使用迁移管理数据库

虽然 synchronize 选项可以自动同步数据库表结构,但在生产环境中,我们通常不推荐使用它,因为这可能会导致意外的数据丢失。相反,我们应该使用迁移功能来手动管理数据库表结构的变化。

1. 创建迁移文件

TypeORM 提供了 migration:create 命令来创建迁移文件。你可以通过以下命令创建一个新的迁移文件:

npx typeorm migration:create -n CreateUsersTable

这会在 src/migration 文件夹下生成一个名为 CreateUsersTable.ts 的文件。打开这个文件,你会看到类似以下的内容:

import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsersTable1627891200000 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    // 在这里编写创建表的 SQL 语句
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    // 在这里编写删除表的 SQL 语句
  }
}

2. 编写迁移逻辑

up 方法中,你可以编写创建表的 SQL 语句。TypeORM 提供了一些便捷的方法来生成 SQL 语句,比如 queryRunner.createTable。你可以使用这些方法来创建表、添加字段、修改字段等。

例如,我们可以使用以下代码来创建 users 表:

public async up(queryRunner: QueryRunner): Promise<void> {
  await queryRunner.createTable(
    new Table({
      name: 'users',
      columns: [
        {
          name: 'id',
          type: 'int',
          isPrimary: true,
          isGenerated: true,
          generationStrategy: 'increment',
        },
        {
          name: 'name',
          type: 'varchar',
          length: '50',
        },
        {
          name: 'email',
          type: 'varchar',
          length: '100',
          isUnique: true,
        },
        {
          name: 'password',
          type: 'varchar',
          length: '60',
        },
        {
          name: 'isActive',
          type: 'boolean',
          default: true,
        },
      ],
    }),
    true
  );
}

public async down(queryRunner: QueryRunner): Promise<void> {
  await queryRunner.dropTable('users');
}

3. 运行迁移

编写完迁移逻辑后,你可以使用 migration:run 命令来执行迁移:

npx typeorm migration:run

这会依次执行所有未执行的迁移文件,创建或修改数据库表结构。如果你想回滚迁移,可以使用 migration:revert 命令:

npx typeorm migration:revert

这会撤销最近一次执行的迁移。

处理复杂查询

TypeORM 提供了多种方式来处理复杂的数据库查询。除了基本的 findfindOnesavedelete 方法外,TypeORM 还支持更高级的查询构造器和原生 SQL 查询。

1. 使用查询构造器

查询构造器(Query Builder)是 TypeORM 提供的一种灵活的查询方式。它可以让你构建复杂的查询语句,而不需要直接编写 SQL 代码。查询构造器支持多种操作,比如 selectwherejoinorderBy 等。

例如,我们可以使用查询构造器来查找所有活跃的用户,并按姓名排序:

const users = await connection
  .createQueryBuilder()
  .select('user')
  .from(User, 'user')
  .where('user.isActive = :isActive', { isActive: true })
  .orderBy('user.name', 'ASC')
  .getMany();

2. 使用原生 SQL 查询

如果你需要执行非常复杂的查询,或者 TypeORM 的查询构造器无法满足你的需求,你可以直接使用原生 SQL 查询。TypeORM 提供了 query 方法来执行任意的 SQL 语句。

例如,我们可以使用原生 SQL 查询来查找所有用户的总数:

const [users, total] = await connection
  .createQueryBuilder()
  .select('user')
  .from(User, 'user')
  .getManyAndCount();

或者,你也可以直接执行 SQL 语句:

const result = await connection.query('SELECT COUNT(*) FROM users');

3. 使用关系查询

TypeORM 支持一对多、多对一、多对多等关系查询。你可以通过在实体类中定义关系来实现关联查询。

例如,假设我们有一个 Post 实体类,每个帖子都有一个作者(即 User 实体类)。我们可以在 Post 实体类中定义一个 ManyToOne 关系:

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './User';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

然后,在 User 实体类中定义一个 OneToMany 关系:

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Post } from './Post';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 50 })
  name: string;

  @Column({ length: 100, unique: true })
  email: string;

  @Column({ length: 60 })
  password: string;

  @Column({ default: true })
  isActive: boolean;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

现在,你可以通过 User 实体类来查询某个用户的所有帖子,或者通过 Post 实体类来查询某个帖子的作者。

优化性能

随着项目的规模越来越大,API 的性能优化变得越来越重要。TypeORM 提供了一些内置的功能来帮助你优化数据库访问的性能。

1. 使用连接池

TypeORM 内置了连接池机制,可以有效提高数据库访问的性能。连接池会预先创建多个数据库连接,并在需要时复用这些连接,从而减少了连接建立的时间。

你可以在 ormconfig.json 中配置连接池的大小:

{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "username": "your_username",
  "password": "your_password",
  "database": "your_database",
  "synchronize": true,
  "logging": false,
  "entities": ["src/entity/**/*.ts"],
  "migrations": ["src/migration/**/*.ts"],
  "subscribers": ["src/subscriber/**/*.ts"],
  "pool": {
    "max": 10,
    "min": 0,
    "idleTimeoutMillis": 30000
  }
}

2. 使用缓存

TypeORM 支持多种缓存机制,比如内存缓存、Redis 缓存等。你可以通过配置 cache 选项来启用缓存:

{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "username": "your_username",
  "password": "your_password",
  "database": "your_database",
  "synchronize": true,
  "logging": false,
  "entities": ["src/entity/**/*.ts"],
  "migrations": ["src/migration/**/*.ts"],
  "subscribers": ["src/subscriber/**/*.ts"],
  "cache": {
    "type": "redis",
    "duration": 10000
  }
}

3. 使用分页

对于返回大量数据的 API,分页是一个很好的优化手段。TypeORM 提供了 skiptake 方法来实现分页查询。

例如,我们可以使用以下代码来实现分页查询:

const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;

const [users, total] = await userRepository.findAndCount({
  skip: offset,
  take: limit,
});

res.send({
  page,
  limit,
  total,
  data: users,
});

总结

恭喜你,终于走到了文章的结尾!通过今天的讲座,我们学习了如何使用 TypeScript 和 TypeORM 构建一个健壮的 Node.js API。我们从环境搭建开始,逐步介绍了如何创建实体、编写 API 服务器、使用迁移管理数据库、处理复杂查询以及优化性能。希望这篇文章能帮助你在未来的项目中更加得心应手地使用这些技术。

如果你有任何问题或建议,欢迎随时留言交流。感谢大家的聆听,期待下次再见!🚀


附录:常用命令总结

命令 描述
npm init -y 初始化项目
npx typeorm schema:sync 同步数据库表结构
npx typeorm migration:create -n <name> 创建迁移文件
npx typeorm migration:run 执行迁移
npx typeorm migration:revert 回滚迁移
npx ts-node src/app.ts 启动服务器

附录:常用装饰器总结

装饰器 描述
@Entity() 标记类为实体
@PrimaryGeneratedColumn() 标记字段为主键并自增
@Column() 标记字段为普通列
@ManyToOne() 定义多对一关系
@OneToMany() 定义一对多关系
@ManyToMany() 定义多对多关系

希望这篇文章对你有所帮助,祝你在开发过程中一切顺利!🌟

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注