Миграции базы данных
Миграции — это версионируемые SQL-скрипты (или JS-файлы), описывающие изменения схемы базы данных, которые можно применить (up) или откатить (down) в определённом порядке.
Зачем нужно
Без миграций изменения схемы БД применяются вручную на каждом окружении (dev, staging, production), что приводит к рассинхронизации схем. Миграции хранятся в git рядом с кодом: каждое изменение схемы атомарно связано с изменением кода, легко воспроизводится командой и деплоится автоматически в CI/CD.
Где используется
- Любой проект с реляционной БД (PostgreSQL, MySQL, SQLite)
- CI/CD — автоматическое применение миграций перед деплоем
- Онбординг нового разработчика —
npm run migrateподнимает актуальную схему - Rollback при проблемном деплое
Основной контент
Prisma Migrate (рекомендуется с Prisma ORM)
# Создать и применить миграцию (dev)
npx prisma migrate dev --name add_user_table
# Применить все pending миграции (production)
npx prisma migrate deploy
# Посмотреть статус миграций
npx prisma migrate status
# Откатить последнюю (не поддерживается напрямую — нужен ручной SQL)
prisma/
migrations/
20240101000000_init/
migration.sql
20240115000000_add_user_table/
migration.sql
schema.prisma
Knex.js Migrations (без ORM)
npm install knex
npx knex init # создать knexfile.js
npx knex migrate:make create_users_table
// knexfile.js
module.exports = {
development: {
client: 'postgresql',
connection: process.env.DATABASE_URL,
migrations: { directory: './db/migrations' },
seeds: { directory: './db/seeds' }
},
production: {
client: 'postgresql',
connection: process.env.DATABASE_URL,
migrations: { directory: './db/migrations' }
}
};
// db/migrations/20240101_create_users.js
exports.up = async (knex) => {
await knex.schema.createTable('users', (table) => {
table.increments('id').primary;
table.string('name', 100).notNullable;
table.string('email', 255).notNullable.unique;
table.string('password_hash', 255).notNullable;
table.enum('role', ['user', 'admin']).defaultTo('user');
table.timestamps(true, true); // created_at, updated_at
});
};
exports.down = async (knex) => {
await knex.schema.dropTableIfExists('users');
};
// Добавление колонки в существующую таблицу
// db/migrations/20240115_add_avatar_to_users.js
exports.up = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.string('avatar_url').nullable;
table.index(['email']); // добавить индекс
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.dropColumn('avatar_url');
table.dropIndex(['email']);
});
};
# Применить все pending миграции
npx knex migrate:latest
# Откатить последний batch
npx knex migrate:rollback
# Статус миграций
npx knex migrate:status
Seeds — заполнение тестовыми данными
// db/seeds/01_users.js
exports.seed = async (knex) => {
await knex('users').del; // очистить таблицу
await knex('users').insert([
{ name: 'Admin', email: 'admin@example.com', role: 'admin', password_hash: '...' },
{ name: 'Alice', email: 'alice@example.com', role: 'user', password_hash: '...' }
]);
};
npx knex seed:run
npm scripts для CI/CD
{
"scripts": {
"migrate": "npx knex migrate:latest",
"migrate:rollback": "npx knex migrate:rollback",
"seed": "npx knex seed:run",
"db:reset": "npx knex migrate:rollback --all && npx knex migrate:latest && npx knex seed:run"
}
}
Частые ошибки
- Редактировать применённые миграции — если миграция уже применена на production, менять её нельзя; создавать новую
- Не хранить rollback — функция
downобязательна для возможности отката - Destructive миграции без backup —
DROP TABLE,DROP COLUMNудаляют данные необратимо; делать backup перед применением - Нет транзакции — миграцию нужно выполнять в транзакции: если она упадёт на половине, БД останется в неконсистентном состоянии