Migrations
Migrations allow for you to define sets of schema changes so upgrading a database is a breeze.
Migration CLI
The migration CLI is bundled with the knex install, and is driven by the node-liftoff module. To install globally, run:
$ npm install knex -g
The migration CLI accepts the following general command-line options. You can view help text and additional options for each command using --help
. E.g. knex migrate:latest --help
.
--debug
: Run with debugging--knexfile [path]
: Specify the knexfile path--knexpath [path]
: Specify the path to the knex instance--cwd [path]
: Specify the working directory--client [name]
: Set the DB client--connection [address]
: Set the DB connection--migrations-table-name
: Set the migration table name--migrations-directory
: Set the migrations directory--env
: environment, default:process.
env.NODE_ENV || development --esm
: Enables ESM module interoperability--help
: Display help text for a particular command and exit.
Migrations use a knexfile, which specify various configuration settings for the module. To create a new knexfile, run the following:
$ knex init
# or for .ts
$ knex init -x ts
will create a sample knexfile.js - the file which contains our various database configurations. Once you have a knexfile.js, you can use the migration tool to create migration files to the specified directory (default migrations). Creating new migration files can be achieved by running:
$ knex migrate:make migration_name
# or for .ts
$ knex migrate:make migration_name -x ts
- you can also create your migration using a specific stub file, this serves as a migration template to speed up development for common migration operations
- if the --stub option is not passed, the CLI will use either the knex default stub for the chosen extension, or the config.stub file
$ knex migrate:make --stub
# or
$ knex migrate:make --stub
- if a stub path is provided, it must be relative to the knexfile.[js, ts, etc] location
- if a is used, the stub is selected by its file name. The CLI will look for this file in the config.migrations.directory folder. If the config.migrations.directory is not defined, this operation will fail
Once you have finished writing the migrations, you can update the database matching your NODE_ENV
by running:
$ knex migrate:latest
You can also pass the --env
flag or set NODE_ENV
to select an alternative environment:
$ knex migrate:latest --env production
# or
$ NODE_ENV=production knex migrate:latest
To rollback the last batch of migrations:
$ knex migrate:rollback
To rollback all the completed migrations:
$ knex migrate:rollback --all
To run the next migration that has not yet been run
$ knex migrate:up
To run the specified migration that has not yet been run
$ knex migrate:up 001_migration_name.js
To undo the last migration that was run
$ knex migrate:down
To undo the specified migration that was run
$ knex migrate:down 001_migration_name.js
To list both completed and pending migrations:
$ knex migrate:list
Seed files
Seed files allow you to populate your database with test or seed data independent of your migration files.
Seed CLI
To create a seed file, run:
$ knex seed:make seed_name
Seed files are created in the directory specified in your knexfile.js for the current environment. A sample seed configuration looks like:
module.exports = {
// ...
development: {
client: {/* ... */},
connection: {/* ... */},
seeds: {
directory: './seeds/dev'
}
}
// ...
}
If no seeds.directory
is defined, files are created in ./seeds
. Note that the seed directory needs to be a relative path. Absolute paths are not supported (nor is it good practice).
To run seed files, execute:
$ knex seed:run
Seed files are executed in alphabetical order. Unlike migrations, every seed file will be executed when you run the command. You should design your seed files to reset tables as needed before inserting data.
To run specific seed files, execute:
$ knex seed:run --specific=seed-filename.js --specific=another-seed-filename.js
knexfile.js
A knexfile.js generally contains all of the configuration for your database. It can optionally provide different configuration for different environments. You may pass a --knexfile
option to any of the command line statements to specify an alternate path to your knexfile.
Basic configuration
module.exports = {
client: 'pg',
connection: process.env.DATABASE_URL || {
user: 'me',
database: 'my_app'
}
};
You can also use an async function to get connection details for your configuration. This is useful when you need to fetch credentials from a secure location like vault.
const getPassword = async () => {
// TODO: implement me
return 'my_pass'
}
module.exports = {
client: 'pg',
connection: async () => {
const password = await getPassword()
return { user: 'me', password }
},
migrations: {}
};
Environment configuration
module.exports = {
development: {
client: 'pg',
connection: { user: 'me', database: 'my_app' }
},
production: {
client: 'pg',
connection: process.env.DATABASE_URL
}
};
Custom migration
You may provide a custom migration stub to be used in place of the default option.
module.exports = {
client: 'pg',
migrations: {
stub: 'migration.stub'
}
};
Custom migration name
You may provide a custom migration name to be used in place of the default option.
module.exports = {
client: 'pg',
migrations: {
getNewMigrationName: (name) => {
return `${+new Date()}-${name}.js`;
}
}
};
Generated migration extension
You can control extension of generated migrations.
module.exports = {
client: 'pg',
migrations: {
extension: 'ts'
}
};
Knexfile in other languages
Knex uses Liftoff to support knexfile written in other compile-to-js languages.
Depending on the language, this may require you to install additional dependencies. The complete list of dependencies for each supported language can be found here.
Most common cases are typescript (for which typescript and ts-node packages are recommended), and coffeescript (for which coffeescript dependency is required).
If you don't specify the extension explicitly, the extension of generated migrations/seed files will be inferred from the knexfile extension
Migration API
knex.migrate
is the class utilized by the knex migrations cli.
Each method takes an optional config
object, which may specify the following properties:
directory
: a relative path to the directory containing the migration files. Can be an array of paths (default./migrations
)extension
: the file extension used for the generated migration files (defaultjs
)tableName
: the table name used for storing the migration state (defaultknex_migrations
)schemaName
: the schema name used for storing the table with migration state (optional parameter, only works on DBs that support multiple schemas in a single DB, such as PostgreSQL)disableTransactions
: don't run migrations inside transactions (defaultfalse
)disableMigrationsListValidation
: do not validate that all the already executed migrations are still present in migration directories (defaultfalse
)sortDirsSeparately
: if true and multiple directories are specified, all migrations from a single directory will be executed before executing migrations in the next folder (defaultfalse
)loadExtensions
: array of file extensions which knex will treat as migrations. For example, if you have typescript transpiled into javascript in the same folder, you want to execute only javascript migrations. In this case, setloadExtensions
to['.js']
(Notice the dot!) (default['.co', '.coffee', '.eg', '.iced', '.js', '.litcoffee', '.ls', '.ts']
)migrationSource
: specify a custom migration source, see Custom Migration Source for more info (default filesystem)
Transactions in migrations
By default, each migration is run inside a transaction. Whenever needed, one can disable transactions for all migrations via the common migration config option config.disableTransactions
or per-migration, via exposing a boolean property config.transaction
from a migration file:
exports.up = function(knex) {
return knex.schema
.createTable('users', function (table) {
table.increments('id');
table.string('first_name', 255).notNullable();
table.string('last_name', 255).notNullable();
})
.createTable('products', function (table) {
table.increments('id');
table.decimal('price').notNullable();
table.string('name', 1000).notNullable();
});
};
exports.down = function(knex) {
return knex.schema
.dropTable("products")
.dropTable("users");
};
exports.config = { transaction: false };
The same config property can be used for enabling transaction per-migration in case the common configuration has disableTransactions: true
.
make
knex.migrate.make(name, [config])
Creates a new migration, with the name of the migration being added.
latest
knex.migrate.latest([config])
Runs all migrations that have not yet been run.
If you need to run something only after all migrations have finished their execution, you can do something like this:
knex.migrate.latest()
.then(function() {
return knex.seed.run();
})
.then(function() {
// migrations are finished
});
rollback
knex.migrate.rollback([config], [all])
Rolls back the latest migration group. If the all
parameter is truthy, all applied migrations will be rolled back instead of just the last batch. The default value for this parameter is false
.
up
knex.migrate.up([config])
Runs the specified (by config.name
parameter) or the next chronological migration that has not yet be run.
down
knex.migrate.down([config])
Will undo the specified (by config.name
parameter) or the last migration that was run.
currentVersion
knex.migrate.currentVersion([config])
Retrieves and returns the current migration version, as a promise. If there aren't any migrations run yet, returns "none" as the value for the currentVersion.
list
knex.migrate.list([config])
Will return list of completed and pending migrations
unlock
knex.migrate.forceFreeMigrationsLock([config])
Forcibly unlocks the migrations lock table, and ensures that there is only one row in it.
Notes about locks
A lock system is there to prevent multiple processes from running the same migration batch in the same time. When a batch of migrations is about to be run, the migration system first tries to get a lock using a SELECT ... FOR UPDATE
statement (preventing race conditions from happening). If it can get a lock, the migration batch will run. If it can't, it will wait until the lock is released.
Please note that if your process unfortunately crashes, the lock will have to be manually removed with knex migrate:unlock
in order to let migrations run again.
The locks are saved in a table called "tableName
_lock"; it has a column called is_locked
that knex migrate:unlock
sets to 0
in order to release the lock. The index
column in the lock table exists for compatibility with some database clusters that require a primary key, but is otherwise unused. There must be only one row in this table, or an error will be thrown when running migrations: "Migration table is already locked". Run knex migrate:unlock
to ensure that there is only one row in the table.
Custom migration sources
Knex supports custom migration sources, allowing you full control of where your migrations come from. This can be useful for custom folder structures, when bundling with webpack/browserify and other scenarios.
// Create a custom migration source class
class MyMigrationSource {
// Must return a Promise containing a list of migrations.
// Migrations can be whatever you want,
// they will be passed as arguments to getMigrationName
// and getMigration
getMigrations() {
// In this example we are just returning migration names
return Promise.resolve(['migration1'])
}
getMigrationName(migration) {
return migration;
}
getMigration(migration) {
switch(migration) {
case 'migration1':
return {
up(knex) { /* ... */ },
down(knex) { /* ... */ },
}
}
}
}
// pass an instance of your migration source as knex config
knex.migrate.latest({
migrationSource: new MyMigrationSource()
})
Webpack migration source example
An example of how to create a migration source where migrations are included in a webpack bundle.
const path = require('path')
class WebpackMigrationSource {
constructor(migrationContext) {
this.migrationContext = migrationContext
}
getMigrations() {
return Promise.resolve(
this.migrationContext.keys().sort()
)
}
getMigrationName(migration) {
return path.parse(migration).base
}
getMigration(migration) {
return this.migrationContext(migration)
}
}
// pass an instance of your migration source as knex config
knex.migrate.latest({
migrationSource: new WebpackMigrationSource(
require.context('./migrations', false, /.js$/)
)
})
// with webpack >=5, require.context will add
// both the relative and absolute paths to the context
// to avoid duplicate migration errors, you'll need
// to filter out one or the other this example filters
// out absolute paths, leaving only the relative
// ones(./migrations/*.js):
knex.migrate.latest({
migrationSource: new WebpackMigrationSource(
require.context('./migrations', false, /^\.\/.*\.js$/)
)
})
ECMAScript modules (ESM) Interoperability
ECMAScript Module support for knex CLI's configuration, migration and seeds
enabled by the --esm
flag, ECMAScript Interoperability is provided by the 'esm' module.
You can find here more information about 'esm' superpowers.
Node 'mjs' files are handled by NodeJS own import mechanics
and do not require the use of the '--esm' flag.
But you might need it anyway for Node v10 under certain scenarios.
You can find details about NodeJS ECMAScript modules here
While it is possible to mix and match different module formats (extensions)
between your knexfile, seeds and migrations,
some format combinations will require specific NodeJS versions,
Notably mjs/cjs files will follow NodeJS import and require restrictions.
You can see here many possible scenarios,
and here some sample configurations
Node v10.* require the use of the '--experimental-module' flag in order to use the 'mjs' or 'cjs' extension.
# launching knex on Node v10 to use mjs/cjs modules
node --experimental-modules ./node_modules/.bin/knex $@
When using migration and seed files with '.cjs' or '.mjs' extensions, you will need to specify that explicitly:
/**
* knexfile.mjs
*/
export default {
migrations: {
// ... client, connection,etc ....
directory: './migrations',
loadExtensions: ['.mjs'] //
}
}
When using '.mjs' extensions for your knexfile and '.js' for the seeds/migrations, you will need to specify that explicitly.
/**
* knexfile.mjs
*/
export default {
migrations: {
// ... client, connection,etc ....
directory: './migrations',
loadExtensions: ['.js'] // knex will search for 'mjs' file by default
}
}
For the knexfile you can use a default export,
it will take precedence over named export.
/**
* filename: knexfile.js
* For the knexfile you can use a default export
**/
export default {
client: 'sqlite3',
connection: {
filename: '../test.sqlite3',
},
migrations: {
directory: './migrations',
},
seeds: {
directory: './seeds',
},
}
/**
* filename: knexfile.js
* Let knex find the configuration by providing named exports,
* but if exported a default, it will take precedence, and it will be used instead
**/
const config = {
client: 'sqlite3',
connection: {
filename: '../test.sqlite3',
},
migrations: {
directory: './migrations',
},
seeds: {
directory: './seeds',
},
};
/** this will be used, it has precedence over named export */
export default config;
/** Named exports, will be used if you didn't provide a default export */
export const { client, connection, migrations, seeds } = config;
Seed and migration files need to follow Knex conventions
// file: seed.js
/**
* Same as with the CommonJS modules
* You will need to export a "seed" named function.
* */
export function seed(knex) {
// ... seed logic here
}
// file: migration.js
/**
* Same as the CommonJS version, the miration file should export
* "up" and "down" named functions
*/
export function up(knex) {
// ... migration logic here
}
export function down(knex) {
// ... migration logic here
}
Seed API
knex.seed
is the class utilized by the knex seed CLI.
Each method takes an optional config
object, which may specify the following properties:
directory
: a relative path to the directory containing the seed files. Can be an array of paths (default./seeds
)loadExtensions
: array of file extensions which knex will treat as seeds. For example, if you have typescript transpiled into javascript in the same folder, you want to execute only javascript seeds. In this case, setloadExtensions
to['.js']
(Notice the dot!) (default['.co', '.coffee', '.eg', '.iced', '.js', '.litcoffee', '.ls', '.ts']
)recursive
: if true, will find seed files recursively in the directory / directories specifiedspecific
: a specific seed file or an array of seed files to run from the seeds directory, if its value isundefined
it will run all the seeds (defaultundefined
). If an array is specified, seed files will be run in the same order as the arraysortDirsSeparately
: if true and multiple directories are specified, all seeds from a single directory will be executed before executing seeds in the next folder (defaultfalse
)seedSource
: specify a custom seed source, see Custom Seed Source for more info (default filesystem)extension
: extension to be used for newly generated seeds (defaultjs
)timestampFilenamePrefix
: whether timestamp should be added as a prefix for newly generated seeds (defaultfalse
)
make
knex.seed.make(name, [config])
Creates a new seed file, with the name of the seed file being added. If the seed directory config is an array of paths, the seed file will be generated in the latest specified.
run
knex.seed.run([config])
Runs all seed files for the current environment.
Custom seed sources
Knex supports custom seed sources, allowing you full control of where your seeds come from. This can be useful for custom folder structures, when bundling with webpack/browserify and other scenarios.
// Create a custom seed source class
class MySeedSource {
// Must return a Promise containing a list of seeds.
// Seeds can be whatever you want, they will be passed as
// arguments to getSeed
getSeeds() {
// In this example we are just returning seed names
return Promise.resolve(['seed1'])
}
getSeed(seed) {
switch(seed) {
case 'seed1':
return (knex) => { /* ... */ }
}
}
}
// pass an instance of your seed source as knex config
knex.seed.run({ seedSource: new MySeedSource() })