数据库迁移是对数据库进行的修改。这些修改可能包括更改表的模式、更新一组记录中的数据、播种数据或删除一系列记录。

数据库迁移通常在应用程序启动之前运行,对于同一个数据库,数据库迁移通常只能成功运行一次。数据库迁移工具会保存已在数据库中运行的迁移历史,以便将来可以跟踪。

在本文中,您将学习如何在一个简单的 Node.js API 应用程序中设置和运行数据库迁移。我们将使用 ts-migrate-mongoose 和一个 npm 脚本来创建迁移,并将数据播种到 MongoDB 数据库中。ts-migrate-mongoose 支持从 TypeScript 代码以及 CommonJS 代码运行迁移脚本。

ts-migrate-mongoose 是用于使用 mongoose 作为对象数据映射器的 Node.js 项目的迁移框架。它提供了编写迁移脚本的模板。它还提供了一个配置,以便从程序中运行脚本以及从 CLI 运行脚本。

目录

如何设置项目

要使用ts-migrate-mongoose进行数据库迁移,您需要具备以下条件:

  1. 一个安装了mongoose依赖的Node.js项目。

  2. 一个连接到项目的MongoDB数据库。

  3. MongoDB Compass(可选 – 用于查看数据库中的更改)。

已创建了一个可从ts-migrate-mongoose-starter-repo克隆的入门存储库,以方便使用。克隆存储库,填写环境变量,并通过运行npm start命令启动应用程序。

使用浏览器或诸如Postman之类的API客户端访问http://localhost:8000,服务器将返回一个“Hello there!”文本,以显示入门应用程序按预期运行。

如何为项目配置ts-migrate-mongoose

要为项目配置ts-migrate-mongoose,请使用以下命令安装ts-migrate-mongoose:

npm install ts-migrate-mongoose

ts-migrate-mongoose允许使用JSON文件、TypeScript文件、.env文件或通过CLI进行配置。建议使用.env文件,因为配置内容可能包含数据库密码,不适合将其暴露给公众。 .env文件通常通过.gitignore文件隐藏,因此更安全。该项目将使用.env文件进行ts-migrate-mongoose配置。

文件应包含以下键和它们的值:

  • MIGRATE_MONGO_URI – Mongo数据库的URI。与数据库URL相同。

  • MIGRATE_MONGO_COLLECTION – 迁移应保存在其中的集合(或表)的名称。默认值为migrations,在此项目中使用。ts-migrate-mongoose将迁移保存到MongoDB。

  • MIGRATE_MIGRATIONS_PATH – 用于存储和读取迁移脚本的文件夹路径。默认值为./migrations,在此项目中使用。

如何使用ts-migrate-mongoose种子用户数据

我们已成功创建了一个项目并成功连接到一个Mongo数据库。此时,我们希望将用户数据种子到数据库中。我们需要:

  1. 创建一个用户集合(或表)

  2. 使用ts-migrate-mongoose创建一个迁移脚本来种子数据

  3. 使用ts-migrate-mongoose运行迁移以在应用程序启动之前将用户数据种子到数据库中

1. 使用 Mongoose 创建一个 users 集合

Mongoose 模式可以用来创建一个用户集合(或表)。用户文档(或记录)将包含以下字段(或列):emailfavouriteEmojiyearOfBirth

要为用户集合创建一个 Mongoose 模式,请在项目根目录中创建一个名为 user.model.js 的文件,其中包含以下代码片段:

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema(
  {
    email: {
      type: String,
      lowercase: true,
      required: true,
    },
    favouriteEmoji: {
      type: String,
      required: true,
    },
    yearOfBirth: {
      type: Number,
      required: true,
    },
  },
  {
    timestamps: true,
  }
);

module.exports.UserModel = mongoose.model("User", userSchema);

2. 使用 ts-migrate-mongoose 创建一个迁移脚本

ts-migrate-mongoose 提供了 CLI 命令,可用于创建迁移脚本。

在项目根文件夹中运行 npx migrate create <name-of-script> 将在 MIGRATE_MIGRATIONS_PATH 文件夹中创建一个脚本(在我们的例子中是 ./migrations)。<name-of-script> 是我们希望在创建时给迁移脚本文件指定的名称。

要创建一个用来种子用户数据的迁移脚本,请运行:

npx migrate create seed-users

该命令将在 ./migrations 文件夹中创建一个名为 <timestamp>-seed-users.ts 的文件。该文件将包含以下代码片段内容:

// 在此处导入您的模型

export async function up (): Promise<void> {
  // 在此处编写迁移
}

export async function down (): Promise<void> {
  // 在此处编写迁移
}

up 函数用于运行迁移。如果需要,down 函数用于撤消 up 函数执行的操作。在我们的情况下,我们正在尝试向数据库中添加用户。up 函数将包含向数据库中添加用户的代码,而 down 函数将包含删除 up 函数中创建的用户的代码。

如果使用 MongoDB Compass 检查数据库,则迁移集合将包含如下文档:

{
  "_id": ObjectId("6744740465519c3bd9c1a7d1"),
  "name": "seed-users",
  "state": "down",
  "createdAt": 2024-11-25T12:56:36.316+00:00,
  "updatedAt": 2024-11-25T12:56:36.316+00:00,
  "__v": 0
}

迁移文档的 state 字段设置为 down。成功运行后,它会更改为 up

您可以将 ./migrations/<timestamp>-seed-users.ts 中的代码更新为下面的片段:

require("dotenv").config() // 加载环境变量
const db = require("../db.js")
const { UserModel } = require("../user.model.js");

const seedUsers = [
  { email: "[email protected]", favouriteEmoji: "🏃", yearOfBirth: 1997 },
  { email: "[email protected]", favouriteEmoji: "🍏", yearOfBirth: 1998 },
];

export async function up (): Promise<void> {
  await db.connect(process.env.MONGO_URI)
  await UserModel.create(seedUsers);}

export async function down (): Promise<void> {
  await db.connect(process.env.MONGO_URI)
  await UserModel.delete({
    email: {
      $in: seedUsers.map((u) => u.email),
    },
  });
}

3. 在应用程序启动之前运行迁移

ts-migrate-mongoose 为我们提供了 CLI 命令,用于运行迁移脚本的 updown 函数。

使用 npx migrate up <script-名称>,我们可以运行特定脚本的 up 函数。使用 npx migrate up,我们可以运行位于数据库中具有 down 状态的 ./migrations 文件夹中所有脚本的 up 函数。

在应用程序启动之前运行迁移,我们利用 npm 脚本。带有前缀pre的 npm 脚本将在没有pre前缀的脚本之前运行。例如,如果有一个dev脚本和一个predev脚本,每当运行npm run dev来运行dev脚本时,predev脚本将在dev脚本运行之前自动运行。

我们将利用 npm 脚本的这一特性,将 ts-migrate-mongoose 命令放在prestart脚本中,以便在运行start脚本之前运行迁移。

更新package.json文件,添加一个prestart脚本,用于运行 ts-migrate-mongoose 命令以运行项目中迁移脚本的up函数。

  "scripts": {
    "prestart": "npx migrate up",
    "start": "node index.js"
  },

通过这种设置,在执行npm run start启动应用程序时,将运行prestart脚本来使用 ts-migrate-mongoose 执行迁移并在应用程序启动之前填充数据库。

在运行npm run start后,您应该会看到类似下面的片段:

Synchronizing database with file system migrations...
MongoDB connection successful
up: 1732543529744-seed-users.ts 
All migrations finished successfully

> [email protected] start
> node index.js

MongoDB connection successful                      
Server listening on port 8000

查看存储库的seed-users分支,以查看文章中此时代码库的当前状态。

如何构建用于获取种子数据的 API 端点

我们可以构建一个 API 端点来获取数据库中种子用户数据。在server.js文件中,将代码更新为下面片段中的代码:

const { UserModel } = require("./user.model.js")

module.exports = async function (req, res) {
  const users = await UserModel.find({}) // 获取数据库中的所有用户

  res.writeHead(200, { "Content-Type": "application/json" });
  return res.end(JSON.stringify({ // 返回获取用户数据的 JSON 表示
    users: users.map((u) => ({
      email: u.email,
      favouriteEmoji: u.favouriteEmoji,
      yearOfBirth: u.yearOfBirth,
      createdAt: u.createdAt
    }))
  }, null, 2));
};

如果我们启动应用程序并使用 Postman 或浏览器访问 http://localhost:8000,我们将获得类似于以下的 JSON 响应:

{
  "users": [
    {
      "email": "[email protected]",
      "favouriteEmoji": "🏃",
      "yearOfBirth": 1997,
      "createdAt": "2024-11-25T14:18:55.416Z"
    },
    {
      "email": "[email protected]",
      "favouriteEmoji": "🍏",
      "yearOfBirth": 1998,
      "createdAt": "2024-11-25T14:18:55.416Z"
    }
  ]
}

请注意,如果应用程序再次运行,迁移脚本将不再运行,因为迁移的 state 在成功运行后将变为 up

查看代码库的 fetch-users 分支,以查看本文当前时刻代码库的状态。

结论

在构建应用程序时,迁移非常有用,尤其是在需要为测试提供初始数据、填充管理用户、通过添加或删除列更新数据库架构,以及一次更新多个记录中的列值时。

如果您在 Node.js 应用程序中使用 Mongoose 和 MongoDB,ts-migrate-mongoose 可以帮助提供运行迁移的框架。