Пошук уроків, статей та іншого контенту
Створюватимете, застосовуватимете й відстежуватимете міграції схеми бази даних у командній розробці.
Міграція бази даних — це версійований файл із послідовністю змін схеми бази даних.
Схема описує структуру даних:
таблиці;
стовпці;
типи даних;
індекси;
зовнішні ключі;
обмеження.
Замість ручного виконання SQL-команд кожен розробник додає зміни до репозиторію у вигляді міграції. Потім ці зміни можна послідовно застосувати до локальної, тестової та production-бази.
Типова міграція має дві операції:
up — застосувати зміну;
down — скасувати зміну.
Наприклад, міграція може створити таблицю users, а її відкат — видалити цю таблицю.
Без міграцій структура бази швидко розходиться між середовищами:
у одного розробника є новий стовпець, а в іншого немає;
тестова база має іншу структуру, ніж production;
незрозуміло, які SQL-команди вже виконувалися;
новому учаснику команди складно налаштувати базу;
зміни неможливо надійно повторити або перевірити.
Міграції вирішують ці проблеми:
Зміни схеми зберігаються разом із кодом.
Кожна зміна має порядок виконання.
Стан бази можна відстежувати.
Усі середовища налаштовуються однаковим способом.
Міграції можна запускати під час CI/CD-деплою.
Для Node.js існує кілька інструментів міграцій. У цьому прикладі використаємо Knex — SQL query builder для Node.js, який також має CLI та систему міграцій.
Встановимо Knex і драйвер SQLite:
npm init -y
npm install knex sqlite3
npx knex initSQLite зручна для навчального прикладу, оскільки не потребує окремого сервера бази даних. У реальному проєкті конфігурація може використовувати PostgreSQL або MySQL, а принцип роботи міграцій залишиться таким самим.
Після виконання npx knex init створюється файл knexfile.js. Налаштуймо в ньому SQLite та каталог міграцій:
// knexfile.js
module.exports = {
development: {
client: 'sqlite3',
connection: {
filename: './app.sqlite3'
},
useNullAsDefault: true,
migrations: {
directory: './migrations'
}
}
};Параметри конфігурації:
client — драйвер бази даних;
connection.filename — шлях до файлу SQLite;
useNullAsDefault — налаштування, потрібне для SQLite;
migrations.directory — каталог із файлами міграцій.
Для PostgreSQL конфігурація зазвичай містить client: 'pg' і параметри підключення до сервера.
Створимо міграцію для таблиці користувачів:
npx knex migrate:make create_users --env developmentKnex створить файл із часовою міткою, наприклад:
migrations/20260818120000_create_users.jsЧасова мітка на початку імені визначає порядок виконання міграцій. Файли виконуються від найстарішого до найновішого.
Заповнімо створений файл:
// migrations/20260818120000_create_users.js
exports.up = function (knex) {
return knex.schema.createTable('users', function (table) {
table.increments('id').primary();
table.string('email', 255).notNullable().unique();
table.string('name', 100).notNullable();
table.timestamp('created_at').notNullable().defaultTo(knex.fn.now());
});
};
exports.down = function (knex) {
return knex.schema.dropTableIfExists('users');
};upup описує зміни, які потрібно застосувати:
створює таблицю users;
додає автоматичний числовий ідентифікатор;
робить email обов’язковим та унікальним;
додає обов’язкове ім’я;
додає дату створення.
downdown описує зворотну зміну. У цьому випадку таблиця видаляється.
down потрібна для:
локального відкату помилкової міграції;
тестування міграції;
повернення схеми до попереднього стану під час розробки.
Важливо: видалення таблиці видаляє і всі дані в ній. Тому відкат у production потрібно виконувати дуже обережно.
Щоб застосувати всі ще не виконані міграції, виконаймо:
npx knex migrate:latest --env developmentKnex:
перевірить каталог міграцій;
визначить, які файли ще не застосовані;
виконає їх у правильному порядку;
збереже інформацію про виконані файли.
Після виконання цієї команди у файлі app.sqlite3 з’явиться таблиця users.
Knex також створює службові таблиці:
knex_migrations — список застосованих міграцій;
knex_migrations_lock — захист від одночасного запуску кількох процесів міграцій.
Застосовану міграцію повторно виконувати не потрібно. Knex визначає її стан за записом у knex_migrations.
Припустімо, користувачам потрібно додати необов’язковий номер телефону:
npx knex migrate:make add_phone_to_users --env developmentФайл міграції може мати такий вигляд:
// migrations/20260818121500_add_phone_to_users.js
exports.up = function (knex) {
return knex.schema.table('users', function (table) {
table.string('phone', 30);
});
};
exports.down = function (knex) {
return knex.schema.table('users', function (table) {
table.dropColumn('phone');
});
};Застосуємо нову міграцію:
npx knex migrate:latest --env developmentKnex не створить таблицю заново. Він виконає лише нову міграцію, якої ще немає у knex_migrations.
Щоб переглянути стан міграцій, використай:
npx knex migrate:status --env developmentКоманда показує, які міграції вже застосовані, а які залишилися невиконаними.
Це корисно перед:
запуском застосунку після оновлення коду;
створенням pull request;
деплоєм;
перевіркою локального середовища.
Також можна отримати список застосованих міграцій програмно:
const knexConfig = require('./knexfile');
const knex = require('knex')(knexConfig.development);
async function main() {
try {
const [completed, pending] = await knex.migrate.list();
console.log('Застосовані міграції:');
console.log(completed.map((item) => item.name));
console.log('Очікують застосування:');
console.log(pending.map((item) => item.fileName));
} finally {
await knex.destroy();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});Метод knex.migrate.list() повертає два списки:
завершені міграції;
міграції, які ще не застосовані.
Щоб скасувати останню групу міграцій, виконай:
npx knex migrate:rollback --env developmentМіграції, застосовані під час одного запуску migrate:latest, утворюють групу. Команда migrate:rollback скасовує останню групу, викликаючи down у зворотному порядку.
Наприклад, якщо останній запуск застосував дві міграції:
create_users
add_phone_to_usersвідкат спочатку виконає down для add_phone_to_users, а потім для create_users.
Щоб скасувати всі групи:
npx knex migrate:rollback --all --env developmentЦю команду не слід запускати без перевірки, оскільки вона може видалити всю схему та дані, які створювалися міграціями.
Багато змін схеми виконуються всередині транзакції. Якщо міграція завершується помилкою, база даних може скасувати її частково виконані зміни.
Однак можливості транзакцій залежать від конкретної СУБД і типу операції. Тому міграцію потрібно перевіряти на тій самій базі даних, яка використовується в production.
Міграції не повинні залежати від даних, які випадково існують у локальній базі. Якщо міграція змінює дані, потрібно явно описати це в ній.
Наприклад, додавання обов’язкового стовпця до таблиці, у якій уже є рядки, може завершитися помилкою: старі рядки не мають значення для нового стовпця.
Безпечніший підхід:
додати стовпець як необов’язковий;
заповнити його для старих записів;
у наступній міграції зробити його обов’язковим.
Файли міграцій мають бути частиною репозиторію разом із кодом застосунку.
Не потрібно додавати до репозиторію:
файл локальної SQLite-бази;
секрети підключення;
службові тимчасові файли.
Якщо міграція вже потрапила до спільної гілки або була застосована в іншому середовищі, не редагуйте її.
Замість цього створіть нову міграцію, яка виправляє попередню зміну:
npx knex migrate:make rename_user_name --env developmentПричина проста: різні середовища могли вже виконати стару версію файлу. Редагування цього файлу призведе до різних схем, хоча список міграцій буде виглядати однаково.
Нова міграція може використовувати структуру, створену попередніми міграціями. Тому порядок файлів важливий.
Не слід:
вручну змінювати часові мітки;
перейменовувати застосовані міграції;
створювати залежність від міграції, яка виконується пізніше.
Міграція має виконувати конкретну зміну і не покладатися на випадковий стан бази.
Добре:
exports.up = function (knex) {
return knex.schema.table('users', function (table) {
table.string('phone', 30);
});
};Небажано поміщати в міграцію логіку, яка залежить від поточних результатів запитів або зовнішніх сервісів. Міграції повинні залишатися відтворюваними.
Типовий порядок деплою:
отримати нову версію коду;
встановити залежності;
виконати міграції;
запустити або перезапустити застосунок.
Наприклад, команда деплою може запускати:
npx knex migrate:latest --env productionProduction-конфігурацію не слід зберігати з паролями у файлі репозиторію. Дані підключення зазвичай передають через змінні середовища.
Важливо, щоб міграції були сумісними з кодом, який тимчасово працює під час оновлення. Наприклад, додавання нового необов’язкового стовпця зазвичай безпечніше, ніж негайне перейменування або видалення стовпця, який ще використовує стара версія застосунку.
Розробник змінює модель даних.
Створює нову міграцію.
Реалізує up і down.
Запускає міграцію локально.
Перевіряє результат і відкат.
Додає файл до commit.
Інші розробники отримують файл і запускають migrate:latest.
CI/CD застосовує міграції на потрібному середовищі.
Приклад набору команд:
# Створити міграцію
npx knex migrate:make create_users --env development
# Застосувати всі нові міграції
npx knex migrate:latest --env development
# Перевірити стан
npx knex migrate:status --env development
# Скасувати останню групу під час локальної розробки
npx knex migrate:rollback --env developmentЯкщо зміну виконати вручну через SQL-клієнт, але не додати міграцію до репозиторію, інші середовища про цю зміну не дізнаються.
Виправлення: усі постійні зміни схеми оформлюйте міграціями.
Застосована міграція вже є частиною історії бази. Її редагування не змінить базу, де вона вже виконана.
Виправлення: створюйте нову міграцію.
downМіграція без down ускладнює локальне тестування та відкат.
Виправлення: для кожної зміни продумайте зворотну операцію. Якщо повний відкат неможливий або небезпечний, це потрібно явно врахувати в процесі розгортання.
Старі рядки не матимуть значення для нового стовпця, тому міграція може завершитися помилкою.
Виправлення: використовуйте поетапну зміну — спочатку необов’язковий стовпець, потім заповнення даних, а після цього додавання обмеження.
Можна випадково застосувати міграції до локальної бази замість тестової або production-бази.
Виправлення:
перевіряйте --env;
розділяйте конфігурації середовищ;
не зберігайте підключення до production у локальних файлах;
перевіряйте базу перед виконанням руйнівних операцій.
Міграція — це версійована зміна схеми бази даних.
up застосовує зміну, а down скасовує її.
Knex зберігає інформацію про виконані міграції у службових таблицях.
migrate:latest застосовує всі невиконані міграції.
migrate:status показує стан міграцій.
migrate:rollback скасовує останню групу міграцій.
Застосовані міграції не слід редагувати — для виправлень потрібно створювати нові.
Файли міграцій мають зберігатися в системі контролю версій.
Перед production-деплоєм міграції потрібно перевіряти на сумісність із поточною версією застосунку.