انتقال دیتابیس (Database migration) به تغییراتی گفته میشود که در یک دیتابیس اعمال میگردند. این تغییرات ممکن است شامل تغییر ساختار جدولها، بهروزرسانی دادههای یک مجموعه از رکوردها، اضافه کردن دادههای اولیه (seeding) یا حذف یک محدوده از رکوردها باشد.
انتقال دیتابیس معمولاً قبل از اجرای برنامه انجام میشود و برای یک دیتابیس مشخص، بیش از یک بار با موفقیت اجرا نمیگردد. ابزارهای مدیریت انتقال دیتابیس، تاریخچهای از تمام انتقالهای اجراشده را ذخیره میکنند تا بتوانند آنها را در آینده ردیابی و مدیریت کنند.
در این مقاله یاد میگیریم که چگونه انتقالهای دیتابیس را در یک برنامه API مینیمال Node.js تنظیم و اجرا کنیم. ما از ts-migrate-mongoose و یک اسکریپت npm برای ایجاد یک انتقال و افزودن دادهها به یک دیتابیس MongoDB استفاده خواهیم کرد.
ts-migrate-mongoose یک فریمورک انتقال برای پروژههای Node.js است که از mongoose بهعنوان Object Data Mapper استفاده میکند. این ابزار، یک قالب برای نوشتن اسکریپتهای انتقال فراهم مینماید و همچنین امکان اجرای این اسکریپتها را، هم بهصورت برنامهنویسی و هم از طریق رابط خط فرمان (CLI) فراهم میسازد.
برای استفاده از ts-migrate-mongoose جهت مدیریت انتقالهای دیتابیس، باید موارد زیر را داشته باشیم:
یک repository شروع که میتوانیم آن را از ts-migrate-mongoose-starter-repo کلون کنیم برای راحتی کار ایجاد شده است. repository را کلون کرده، متغیرهای محیطی را پر میکنیم و با اجرای دستور npm start
اپلیکیشن را راهاندازی مینماییم.
سپس با مرورگر یا یک کلاینت API مانند Postman، به آدرس http://localhost:8000 مراجعه میکنیم. سرور متن "Hello there!"
را return میکند تا نشان دهد که اپلیکیشن به درستی شروع به اجرا شده است.
برای اینکه 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
میباشد که در این پروژه مورد استفاده قرار میگیرد.ما توانستهایم یک پروژه ایجاد کرده و آن را بهطور موفقیتآمیز به یک دیتابیس Mongo متصل کنیم. در این مرحله، میخواهیم دادههای کاربر را به دیتابیس seed کنیم؛ یعنی مقادیر اولیه را در دیتابیس وارد نماییم. برای این کار باید مراحل زیر را انجام دهیم:
میتوانیم از 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 دستورات 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 برای دریافت دادههای کاربران در دیتابیس خود ایجاد نماییم. کد موجود در فایل 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 خود انجام دهیم.