Migrations
MikroORM has integrated support for migrations via umzug. It allows you to generate migrations based on the current schema difference.
To use migrations, you need to first install @mikro-orm/migrations
package for SQL driver or @mikro-orm/migrations-mongodb
for MongoDB, and register the Migrator
extension in your ORM config.
import { Migrator } from '@mikro-orm/migrations'; // or `@mikro-orm/migrations-mongodb`
export default defineConfig({
// ...
extensions: [Migrator],
})
Since v5, migrations are stored without an extension.
By default, each migration will be executed inside a transaction, and all of them will be wrapped in one master transaction, so if one of them fails, everything will be rolled back.
Migration class
Migrations are classes that extend Migration abstract class:
import { Migration } from '@mikro-orm/migrations';
export class Migration20191019195930 extends Migration {
async up(): Promise<void> {
this.addSql('select 1 + 1');
}
}
To support undoing those changed, you can implement the down
method, which throws an error by default.
Migrations are by default wrapped in a transaction. You can override this behaviour on per migration basis by implementing the isTransactional(): boolean
method.
Configuration
object and driver instance are available in the Migration
class context.
You can execute queries in the migration via Migration.execute()
method, which will run queries in the same transaction as the rest of the migration. The Migration.addSql()
method also accepts instances of knex. Knex instance can be accessed via Migration.getKnex()
;
Working with EntityManager
While the purpose of migrations is mainly to alter your SQL schema, you can as well use them to modify your data, either by using this.execute()
, or through an EntityManager
:
Using the EntityManager
in migrations is possible, but discouraged, as it can lead to errors when your metadata change over time, since this will depend on your currently checked out app state, not on the time when the migration was generated. You should prefer using raw queries in your migrations.
import { Migration } from '@mikro-orm/migrations';
import { User } from '../entities/User';
export class Migration20191019195930 extends Migration {
async up(): Promise<void> {
const em = this.getEntityManager();
em.create(User, { ... });
await em.flush();
}
}
Initial migration
This is optional and only needed for the specific use case, when both entities and schema already exist.
If you want to start using migrations, and you already have the schema generated, you can do so by creating so called initial migration:
Initial migration can be created only if there are no migrations previously generated or executed.
npx mikro-orm migration:create --initial
This will create the initial migration, containing the schema dump from schema:create
command. The migration will be automatically marked as executed.
Snapshots
Creating new migration will automatically save the target schema snapshot into migrations folder. This snapshot will be then used if you try to create new migration, instead of using current database schema. This means that if you try to create new migration before you run the pending ones, you still get the right schema diff.
Snapshots should be versioned just like the regular migration files.
Snapshotting can be disabled via migrations.snapshot: false
in the ORM config.
Configuration
Since v5,
umzug
3.0 is used, andpattern
option has been replaced withglob
.
migrations.path
andmigrations.pathTs
works the same way asentities
andentitiesTs
in entity discovery.
await MikroORM.init({
// default values:
migrations: {
tableName: 'mikro_orm_migrations', // name of database table with log of executed transactions
path: './migrations', // path to the folder with migrations
pathTs: undefined, // path to the folder with TS migrations (if used, you should put path to compiled files in `path`)
glob: '!(*.d).{js,ts}', // how to match migration files (all .js and .ts files, but not .d.ts)
transactional: true, // wrap each migration in a transaction
disableForeignKeys: true, // wrap statements with `set foreign_key_checks = 0` or equivalent
allOrNothing: true, // wrap all migrations in master transaction
dropTables: true, // allow to disable table dropping
safe: false, // allow to disable table and column dropping
snapshot: true, // save snapshot when creating new migrations
emit: 'ts', // migration generation mode
generator: TSMigrationGenerator, // migration generator, e.g. to allow custom formatting
},
})
Running migrations in production
In production environment you might want to use compiled migration files. Since v5, this should work almost out of box, all you need to do is to configure the migration path accordingly:
import { MikroORM, Utils } from '@mikro-orm/core';
await MikroORM.init({
migrations: {
path: 'dist/migrations',
pathTs: 'src/migrations',
},
// or alternatively
// migrations: {
// path: Utils.detectTsNode() ? 'src/migrations' : 'dist/migrations',
// },
// ...
});
This should allow using CLI to generate TS migration files (as in CLI you probably have TS support enabled), while using compiled JS files in production, where ts-node is not registered.
Using custom MigrationGenerator
When you generate new migrations, MigrationGenerator
class is responsible for generating the file contents. You can provide your own implementation to do things like formatting the SQL statement.
import { TSMigrationGenerator } from '@mikro-orm/migrations';
import { format } from 'sql-formatter';
class CustomMigrationGenerator extends TSMigrationGenerator {
generateMigrationFile(className: string, diff: { up: string[]; down: string[] }): string {
const comment = '// this file was generated via custom migration generator\n\n';
return comment + super.generateMigrationFile(className, diff);
}
createStatement(sql: string, padLeft: number): string {
sql = format(sql, { language: 'postgresql' });
// a bit of indenting magic
sql = sql.split('\n').map((l, i) => i === 0 ? l : `${' '.repeat(padLeft + 13)}${l}`).join('\n');
return super.createStatement(sql, padLeft);
}
}
await MikroORM.init({
// ...
migrations: {
generator: CustomMigrationGenerator,
},
});