Database: Migrations
See how to create and run database migrations in Athenna Framework.
Introduction
Migrations are like version control for your database, allowing your team to define and share the application's database schema definition. If you have ever had to tell a teammate to manually add a column to their local database schema after pulling in your changes from source control, you've faced the problem that database migrations solve.
Generating migrations
You may use the make:migration Artisan command to generate a database migration. The new migration will be placed in your
Path.migrations()./src/database/migrations
node artisan make:migration FlightsMigration
Migrations templates may be customized using the template customization command.
Migration structure
A migration class contains two methods: up() and down(). The up()
method is used to add new tables, columns, or indexes to your
database, while the down() method should reverse the operations
performed by the up() method.
Within both of these methods, you may use the knex schema builder to
expressively create and modify tables. For example, the following migration creates a flights table:
import { BaseMigration, type DatabaseImpl } from '@athenna/database'
export class FlightsMigration extends BaseMigration {
public tableName = 'flights'
public async up(db: DatabaseImpl) {
return db.createTable(this.tableName, (table) => {
table.increments('id')
table.string('name')
table.string('airline')
table.timestamps(true, true, true)
})
}
public async down(db: DatabaseImpl) {
return db.dropTable(this.tableName)
}
}
Modifying existing tables
Your application will grow, and sooner or later you will need to
change a table that already exists. For that, create a new migration
and use the alterTable() method. It works exactly like
createTable(), but for an existing table:
import { BaseMigration, type DatabaseImpl } from '@athenna/database'
export class AddGateToFlightsMigration extends BaseMigration {
public tableName = 'flights'
public async up(db: DatabaseImpl) {
return db.alterTable(this.tableName, (table) => {
table.string('gate').nullable()
})
}
public async down(db: DatabaseImpl) {
return db.alterTable(this.tableName, (table) => {
table.dropColumn('gate')
})
}
}
Setting the migration connection
If your migration will be interacting with a database connection
other than your application's default database connection,
you should set the static getter connection in your migration:
import { BaseMigration, type DatabaseImpl } from '@athenna/database'
export class FlightsMigration extends BaseMigration {
public static connection() {
return 'postgres'
}
public async up(db: DatabaseImpl) {
// ...
}
public async down(db: DatabaseImpl) {
// ...
}
}
Running migrations
To run all of your outstanding migrations, execute the
migration:run Artisan command:
node artisan migration:run
You can use the --connection option to run migrations
for a specific connection:
node artisan migration:run --connection=postgres
If postgres is your default connection then all the migrations
using the default value in the static connection() method
will run too.
Reverting
To revert all your migrations, you may use the migration:revert
Artisan command. This command will revert all your migrations:
node artisan migration:revert
You can use the --connection option to revert migrations for a
specific connection:
node artisan migration:revert --connection=postgres
If postgres is your default connection than all the migrations
using the default value in the static connection() method
will be reverted too.
Wiping and refreshing the database
While developing, it's very common to want to start from scratch.
The db:wipe command reverts all your migrations and drops the
migrations table, leaving your database clean:
node artisan db:wipe
The db:fresh command goes one step further: it wipes your database
and runs all your migrations again. If you also want to populate it
with your seeders, add the
--with-seeders option:
node artisan db:fresh
node artisan db:fresh --with-seeders
Both commands also accept the --connection option:
node artisan db:fresh --connection=postgres
These commands delete all the data of your database. Never run them against your production database.
When using the mongo driver, db:wipe drops all the collections
of your database and db:fresh doesn't run migrations, since Mongo
doesn't use them.
The migrations table
Athenna keeps track of which migrations have already run in a table
called migrations. If you prefer a different name, set the
migrations.tableName option in your connection configuration inside
Path.config('database.ts')./src/config/database.ts
export default {
connections: {
postgres: {
driver: 'postgres',
// ...
migrations: {
tableName: 'schema_migrations'
}
}
}
}
Migrations are registered in this table without the file extension. This way, running your migrations from your TypeScript source code or from your compiled JavaScript code will never register the same migration twice.
Older versions of @athenna/database used to register migrations
with their extension (.ts or .js). Don't worry, Athenna fixes
these old records automatically before running or reverting your
migrations, removing duplicated ones when needed.
If you still need to run an older version of @athenna/database
against the same database, you can turn this behavior off by setting
migrations.normalizeNames to false in your connection
configuration.