Database Migrations

2 min read ยท last updated at

Relational databases define a schema for the data they persist. Database migrations helps with deterministically creating and editing of this schema.

Manually Editing The Schema

A user in your app might have an email, a password and an id used for internal references. Imagine you already have a local, staging and production database with the described user table. Now due to a new feature, users also need to have a name that they can be referred to, since it is more personal than using their email.

One could simply use a database GUI, edit the table, which would generate something like the query below.

ALTER TABLE users
ADD name varchar(255)

Nice, now your local database has the new column and you can develop the code relying on that new column. When deploying your new code, you now need to rember to also apply those changes to stagig and later to production. Annoying, but maybe doable if you are the only one developing changes and you deploy frequently. Nearly impossible in the context of a team that develops multiple features simultaneously, many of which require changes to the schema. Luckily, many frameworks include database migration tools out of the box that helps you automate all of this.

Working With Migrations

When using a tool for database migrations, you usually create a file representing your changes and check that into version control. The tool then applies the changes in the file and stores an entry somewhere stating that these changes were successfully applied to the schema. When we tell the tool again to execute all migrations, it knows that it does not need to run the migration we created again.

This was just a high-level overview of how migrations work. The next sections dive into more detail and provide examples for each framework.

Creating The File

Most frameworks have a designated folder containing timestamped files representing the migrations.

.
โ””โ”€โ”€ myapp
ย ย  โ””โ”€โ”€ migrations
ย ย  ย ย  โ”œโ”€โ”€ 0001_initial.py
ย ย  ย ย  โ”œโ”€โ”€ 0002_users.py
ย ย  ย ย  โ””โ”€โ”€ 0003_auto_20230909_1539.py
.
โ””โ”€โ”€ database
ย ย  โ””โ”€โ”€ migrations
ย ย  ย ย  โ”œโ”€โ”€ 2014_10_12_000000_create_users_table.php
ย ย  ย ย  โ”œโ”€โ”€ 2014_10_12_100000_create_password_resets_table.php
ย ย  ย ย  โ””โ”€โ”€ 2019_08_19_000000_create_failed_jobs_table.php
.
โ””โ”€โ”€ database
ย ย  โ””โ”€โ”€ migrations
ย ย  ย ย  โ”œโ”€โ”€ 20231203120134_create_posts.rb
ย ย  ย ย  โ”œโ”€โ”€ 20231203120948_create_users.rb
ย ย  ย ย  โ””โ”€โ”€ 20231203121022_create_comments.rb

Files in these folders often contain classes or functions

database/migrations/2014_10_12_000000_create_users_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return class extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email')->unique();
$table->string('password');
});
}
}
db/migrate/20231203120948_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :name
t.string :email
end
end
end

Note that we do not explicitly create the id column here. Rails does that by default.

  • Writing the files by hand
  • Link ORMs and create a concept

Listing The Migration Status

Applying The Migrations

Advanced topics

  • Conflict resolution (alembic has the merge stuff)
  • Seed data