Umzug
!! We are looking for maintainers !!
Umzug is a framework-agnostic migration tool for Node. It provides a clean API for running and rolling back tasks.
- Programmatic API for migrations
- Database agnostic
- Supports logging of migration process
- Supports multiple storages for migration data
Documentation
Minimal Example
The following example uses a Sqlite database through sequelize and persists the migration data in the database itself through the sequelize storage.
const Sequelize = require('sequelize');
const path = require('path');
const Umzug = require('umzug');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: './db.sqlite'
});
const umzug = new Umzug({
migrations: {
path: path.join(__dirname, './migrations'),
params: [
sequelize.getQueryInterface()
]
},
storage: 'sequelize',
storageOptions: { sequelize }
});
(async () => {
await umzug.up();
console.log('All migrations performed successfully');
})();
migrations/00_initial.js
:
const Sequelize = require('sequelize');
module.exports = {
async up(query) {
await query.createTable('users', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
},
name: {
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
type: Sequelize.DATE,
allowNull: false
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false
}
});
},
async down(query) {
await query.dropTable('users');
}
};
Usage
Installation
Umzug is available on npm:
npm install umzug
Umzug instance
It is possible to configure an Umzug instance by passing an object to the constructor. The possible options are:
const Umzug = require('umzug');
const umzug = new Umzug({
storage: 'json',
storageOptions: {},
logging: false,
migrations: {
params: [],
path: 'migrations',
pattern: /^\d+[\w-]+\.js$/,
wrap(migrationFunction) { return migrationFunction; },
customResolver(sqlPath) {
return {
async up() {
await sequelize.query(fs.readFileSync(sqlPath, 'utf8'));
};
}
}
nameFormatter(filePath) {
return path.parse(filePath).name;
}
}
})
Executing migrations
The execute
method is a general purpose function that runs for every specified migrations the respective function.
const migrations = await umzug.execute({
migrations: ['some-id', 'some-other-id'],
method: 'up'
});
Getting all pending migrations
You can get a list of pending/not yet executed migrations like this:
const migrations = await umzug.pending();
Getting all executed migrations
You can get a list of already executed migrations like this:
const migrations = await umzug.executed();
Executing pending migrations
The up
method can be used to execute all pending migrations.
const migrations = await umzug.up();
It is also possible to pass the name of a migration in order to just run the migrations from the current state to the passed migration name (inclusive).
await umzug.up({ to: '20141101203500-task' });
You also have the ability to choose to run migrations from a specific migration, excluding it:
await umzug.up({ from: '20141101203500-task' });
In the above example umzug will execute all the pending migrations found after the specified migration. This is particularly useful if you are using migrations on your native desktop application and you don't need to run past migrations on new installs while they need to run on updated installations.
You can combine from
and to
options to select a specific subset:
await umzug.up({ from: '20141101203500-task', to: '20151201103412-items' });
Running specific migrations while ignoring the right order, can be done like this:
await umzug.up({ migrations: ['20141101203500-task', '20141101203501-task-2'] });
There are also shorthand version of that:
await umzug.up('20141101203500-task');
await umzug.up(['20141101203500-task', '20141101203501-task-2']);
Reverting executed migration
The down
method can be used to revert the last executed migration.
const migration = await umzug.down();
It is possible to pass the name of a migration until which (inclusive) the migrations should be reverted. This allows the reverting of multiple migrations at once.
const migrations = await umzug.down({ to: '20141031080000-task' });
To revert all migrations, you can pass 0 as the to
parameter:
await umzug.down({ to: 0 });
Reverting specific migrations while ignoring the right order, can be done like this:
await umzug.down({ migrations: ['20141101203500-task', '20141101203501-task-2'] });
There are also shorthand versions of that:
await umzug.down('20141101203500-task');
await umzug.down(['20141101203500-task', '20141101203501-task-2']);
Migrations
There are two ways to specify migrations: via files or directly via an array of migrations.
Migration files
A migration file ideally exposes an up
and a down
async functions. They will perform the task of upgrading or downgrading the database.
module.exports = {
up: async () => {
...
},
down: async () => {
...
},
};
Migration files should be located in the same directory, according to the info you gave to the Umzug
constructor.
Direct migrations list
You can also specify directly a list of migrations to the Umzug
constructor. We recommend the usage of the Umzug.migrationsList()
function
as bellow:
const umzug = new Umzug({
migrations: Umzug.migrationsList(
[
{
name: '00-first-migration',
async up(queryInterface) { },
async down(queryInterface) { }
},
{
name: '01-foo-bar-migration',
async up(queryInterface) { },
async down(queryInterface) { }
}
],
[
sequelize.getQueryInterface()
]
)
});
Storages
Storages define where the migration data is stored.
JSON Storage
Using the json
storage will create a JSON file which will contain an array with all the executed migrations. You can specify the path to the file. The default for that is umzug.json
in the working directory of the process.
Options:
{
path: process.cwd() + '/db/sequelize-meta.json'
}
Sequelize Storage
Using the sequelize
storage will create a table in your SQL database called SequelizeMeta
containing an entry for each executed migration. You will have to pass a configured instance of Sequelize or an existing Sequelize model. Optionally you can specify the model name, table name, or column name. All major Sequelize versions are supported.
Options:
{
sequelize: instance,
model: model,
modelName: 'Schema',
tableName: 'Schema',
columnName: 'migration',
columnType: new Sequelize.STRING(100)
}
MongoDB Storage
Using the mongodb
storage will create a collection in your MongoDB database called migrations
containing an entry for each executed migration. You will have either to pass a MongoDB Driver Collection as collection
property. Alternatively you can pass a established MongoDB Driver connection and a collection name.
Options:
{
connection: MongoDBDriverConnection,
collectionName: 'migrations',
collection: MongoDBDriverCollection
}
Custom
In order to use custom storage, you have two options:
Method 1: Pass instance to constructor
You can pass your storage instance to Umzug constructor.
class CustomStorage {
constructor(...) {...}
logMigration(...) {...}
unlogMigration(...) {...}
executed(...) {...}
}
let umzug = new Umzug({ storage: new CustomStorage(...) })
Method 2: Require external module from npmjs.com
Create and publish a module which has to fulfill the following API. You can just pass the name of the module to the configuration and umzug will require it accordingly. The API that needs to be exposed looks like this:
module.exports = class MyStorage {
constructor({ option1: 'defaultValue1' } = {}) {
this.option1 = option1;
},
async logMigration(migrationName) {
},
async unlogMigration(migrationName) {
},
async executed() {
}
}
Events
Umzug is an EventEmitter. Each of the following events will be called with name, migration
as arguments. Events are a convenient place to implement application-specific logic that must run around each migration:
- migrating - A migration is about to be executed.
- migrated - A migration has successfully been executed.
- reverting - A migration is about to be reverted.
- reverted - A migration has successfully been reverted.
Examples
License
See the LICENSE file