انتقال دیتابیس MongoDB با استفاده از ts-migrate-mongoose

انتقال دیتابیس (Database migration) به تغییراتی گفته می‌شود که در یک دیتابیس اعمال می‌گردند. این تغییرات ممکن است شامل تغییر ساختار جدول‌ها، به‌روزرسانی داده‌های یک مجموعه از رکوردها، اضافه کردن داده‌های اولیه (seeding) یا حذف یک محدوده از رکوردها باشد.

انتقال دیتابیس معمولاً قبل از اجرای برنامه انجام می‌شود و برای یک دیتابیس مشخص، بیش از یک بار با موفقیت اجرا نمی‌گردد. ابزارهای مدیریت انتقال دیتابیس، تاریخچه‌ای از تمام انتقال‌های اجراشده را ذخیره می‌کنند تا بتوانند آن‌ها را در آینده ردیابی و مدیریت کنند.

در این مقاله یاد می‌گیریم که چگونه انتقال‌های دیتابیس را در یک برنامه API مینیمال Node.js تنظیم و اجرا کنیم. ما از ts-migrate-mongoose و یک اسکریپت npm برای ایجاد یک انتقال و افزودن داده‌ها به یک دیتابیس MongoDB استفاده خواهیم کرد.

ts-migrate-mongoose یک فریم‌ورک انتقال برای پروژه‌های Node.js است که از mongoose به‌عنوان Object Data Mapper استفاده می‌کند. این ابزار، یک قالب برای نوشتن اسکریپت‌های انتقال فراهم می‌نماید و همچنین امکان اجرای این اسکریپت‌ها را، هم به‌صورت برنامه‌نویسی و هم از طریق رابط خط فرمان (CLI) فراهم می‌سازد.

چگونه باید پروژه را راه‌اندازی کنیم؟

برای استفاده از ts-migrate-mongoose جهت مدیریت انتقال‌های دیتابیس، باید موارد زیر را داشته باشیم:

  1. یک پروژه Node.js با mongoose نصب شده به عنوان وابستگی.
  2. یک دیتابیس MongoDB متصل به پروژه.
  3. MongoDB Compass که یک گزینه اختیاری است و برای مشاهده تغییرات در دیتابیس مورد استفاده قرار می‌گیرد.

یک repository شروع که می‌توانیم آن را از ts-migrate-mongoose-starter-repo کلون کنیم برای راحتی کار ایجاد شده است. repository را کلون کرده، متغیرهای محیطی را پر می‌کنیم و با اجرای دستور npm start اپلیکیشن را راه‌اندازی می‌نماییم.

سپس با مرورگر یا یک کلاینت API مانند Postman، به آدرس http://localhost:8000 مراجعه می‌کنیم. سرور متن "Hello there!" را return می‌کند تا نشان دهد که اپلیکیشن به درستی شروع به اجرا شده است.

چگونه ts-migrate-mongoose را برای پروژه پیکربندی کنیم؟

برای اینکه ts-migrate-mongoose را برای پروژه پیکربندی کنیم، ابتدا با استفاده از دستور زیر آن را نصب می‌نماییم:

npm install ts-migrate-mongoose

ts-migrate-mongoose به ما این امکان را می‌دهد که پیکربندی را با استفاده از فایل  JSON، فایل تایپ اسکریپت، فایل .env یا از طریق CLI انجام دهیم. پیشنهاد می‌شود که از فایل .env استفاده کنیم، زیرا محتویات پیکربندی ممکن است شامل رمز عبور دیتابیس باشد که نمایش آن به‌صورت عمومی کار درستی نیست. فایل‌های .env معمولاً از طریق فایل‌های .gitignore مخفی می‌شوند، بنابراین استفاده از آن‌ها امن‌تر است. در این پروژه از یک فایل .env برای پیکربندی ts-migrate-mongoose استفاده خواهیم کرد.

فایل باید شامل کلیدها و مقادیر زیر باشد:

  • MIGRATE_MONGO_URI، این مورد همان URL دیتابیس ما می‌باشد.
  • MIGRATE_MONGO_COLLECTION، نام کالکشن (یا جدول) که انتقال‌ها باید در آن ذخیره شوند. مقدار پیش‌فرض migrations است که در این پروژه استفاده می‌شود. ts-migrate-mongoose انتقال‌ها را در MongoDB ذخیره می‌کند.
  • MIGRATE_MIGRATIONS_PATH، مسیر پوشه‌ای برای ذخیره و خواندن اسکریپت‌های انتقال است. مقدار پیش‌فرض ./migrations می‌باشد که در این پروژه مورد استفاده قرار می‌گیرد.

چگونه داده‌های اولیه کاربر را با استفاده از ts-migrate-mongoose در دیتابیس وارد کنیم؟

ما توانسته‌ایم یک پروژه ایجاد کرده و آن را به‌طور موفقیت‌آمیز به یک دیتابیس Mongo متصل کنیم. در این مرحله، می‌خواهیم داده‌های کاربر را به دیتابیس seed کنیم؛ یعنی مقادیر اولیه را در دیتابیس وارد نماییم. برای این کار باید مراحل زیر را انجام دهیم:

  1. یک کالکشن (یا جدول) users ایجاد می‌کنیم.
  2. از ts-migrate-mongoose برای ایجاد یک اسکریپت انتقال (migration) برای seed کردن داده‌ها استفاده می‌کنیم.
  3. از ts-migrate-mongoose برای اجرای اسکریپت انتقال به منظور seed کردن داده‌های کاربران در دیتابیس، قبل از شروع اجرای اپلیکیشن استفاده می‌کنیم.

۱- ایجاد یک کالکشن users با استفاده از Mongoose

می‌توانیم از Mongoose schema برای ساختن کالکشن (یا جدول) users استفاده کنیم. به منظور ایجاد یک Mongoose schema برای کالکشن users، می‌توانیم یک فایل به نام user.model.js در root پروژه بسازیم و کد زیر را در آن قرار دهیم. این کد ساختار داده‌ای برای کاربران را تعریف می‌کند که شامل فیلدهای email، favouriteEmoji و yearOfBirth است:

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);

۲- ایجاد اسکریپت انتقال با استفاده از ts-migrate-mongoose

ts-migrate-mongoose دستورات CLI را فراهم می‌کند که می‌توانیم از آن‌ها برای ایجاد اسکریپت‌های انتقال استفاده کنیم.

اجرای دستور npx migrate create <name-of-script> در پوشه root پروژه، یک اسکریپت انتقال در پوشه MIGRATE_MIGRATIONS_PATH ( که در اینجا به صورت پیش‌فرض ./migrations است) ایجاد می‌کند. <name-of-script> نامی است که می‌خواهیم برای فایل اسکریپت انتقال تعیین کنیم.

برای ایجاد یک اسکریپت انتقال به منظور اضافه کردن داده‌های اولیه کاربران، دستور زیر را اجرا می‌کنیم:

npx migrate create seed-users

این دستور یک فایل در پوشه ./migrations با نامی به فرم <timestamp>-seed-users.ts ایجاد می‌کند. محتوای این فایل به صورت زیر خواهد بود:

// Import your models here

export async function up (): Promise<void> {
  // Write migration here
}

export async function down (): Promise<void> {
  // Write migration here
}

تابع 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() // load env variables
const db = require("../db.js")
const { UserModel } = require("../user.model.js");

const seedUsers = [
  { email: "john@email.com", favouriteEmoji: "🏃", yearOfBirth: 1997 },
  { email: "jane@email.com", 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),
    },
  });
}

۳- اجرای انتقال قبل از شروع برنامه

ts-migrate-mongoose دستورات CLI را برای اجرای تابع up و down در اسکریپت‌های انتقال فراهم می‌کند.

با اجرای دستور npx migrate up <name-of-script> می‌توانیم تابع up یک اسکریپت خاص را اجرا کنیم. همچنین با دستور npx migrate up می‌توانیم تابع up تمام اسکریپت‌های موجود در پوشه ./migrations را که state آن‌ها در دیتابیس برابر با down است، اجرا نماییم.

برای اجرای انتقال پیش از شروع برنامه، می‌توانیم از اسکریپت‌های npm استفاده کنیم. اسکریپت‌های npm که پیشوند pre دارند، پیش از اسکریپت‌هایی که این پیشوند را ندارند، اجرا می‌شوند. برای مثال، اگر اسکریپت‌های dev و predev را داشته باشیم، زمانی که اسکریپت dev با دستور npm run 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

> ts-migrate-mongoose-starter-repo@1.0.0 start
> node index.js

MongoDB connection successful                      
Server listening on port 8000

برای مشاهده state فعلی پایگاه کد در این قسمت از مقاله، می‌توانیم seed-users branch در repository را بررسی نماییم.

چگونه یک API Endpoint برای دریافت داده‌های اولیه ایجاد کنیم؟

ما می‌توانیم یک API Endpoint برای دریافت داده‌های کاربران در دیتابیس خود ایجاد نماییم. کد موجود در فایل server.js را با کد زیر به‌روزرسانی می‌کنیم:

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

module.exports = async function (req, res) {
  const users = await UserModel.find({}) // fetch all the users in the database

  res.writeHead(200, { "Content-Type": "application/json" });
  return res.end(JSON.stringify({ // return a JSON representation of the fetched users data
    users: users.map((u) => ({
      email: u.email,
      favouriteEmoji: u.favouriteEmoji,
      yearOfBirth: u.yearOfBirth,
      createdAt: u.createdAt
    }))
  }, null, 2));
};

اگر برنامه را راه‌اندازی کرده و به آدرس http://localhost:8000 در Postman یا مرورگر مراجعه کنیم، یک پاسخ JSON مشابه نمونه زیر دریافت خواهیم کرد:

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

باید به این نکته توجه داشته باشیم که اگر برنامه دوباره اجرا شود، اسکریپت انتقال دیگر اجرا نخواهد شد؛ زیرا state انتقال پس از اجرای موفقیت‌آمیز آن، به up تغییر می‌کند.

برای مشاهده state فعلی کد در این مرحله از مقاله، باید به fetch-users branch در repository پروژه مراجعه نماییم.

جمع‌بندی

به طور کلی، انتقال دیتابیس زمانی کاربرد دارد که بخواهیم در حین توسعه برنامه‌ها، داده‌های اولیه‌ای را به منظور تست برنامه وارد کنیم، کاربران مدیریتی اضافه نماییم، ساختار دیتابیس را با اضافه کردن یا حذف ستون‌ها تغییر دهیم و یا این که مقادیر ستون‌ها را در تعداد زیادی رکورد به‌روزرسانی نماییم.

ابزار ts-migrate-mongoose می‌تواند به ما کمک کند تا اگر از Mongoose و MongoDB استفاده می‌کنیم، به راحتی انتقال دیتابیس را در برنامه‌های Node.js خود انجام دهیم.

دیدگاه‌ها:

افزودن دیدگاه جدید