Overview
Migration providers supply migrations to the Migrator class. Kysely provides a built-in FileMigrationProvider for Node.js that reads migrations from a folder, but you can implement custom providers for other use cases.
MigrationProvider Interface
The base interface that all migration providers must implement.
getMigrations()
getMigrations(): Promise<Record<string, Migration>>
Returns all migrations, both old and new.
The keys of the returned object are migration names and values are the migrations. The order of migrations is determined by the alphabetical order of the migration names. The items in the object don’t need to be sorted - Kysely sorts them automatically.
Example Implementation
class CustomMigrationProvider implements MigrationProvider {
async getMigrations(): Promise<Record<string, Migration>> {
return {
'001_initial': {
async up(db) {
// migration code
},
async down(db) {
// rollback code
}
},
'002_add_users': {
async up(db) {
// migration code
}
}
}
}
}
FileMigrationProvider
Reads all migrations from a folder in Node.js.
This provider automatically imports migration files with the following extensions:
.js
.ts (excluding .d.ts)
.mjs
.mts (excluding .d.mts)
Constructor
new FileMigrationProvider(props: FileMigrationProviderProps)
Configuration
FileMigrationProviderProps
fs
FileMigrationProviderFS
required
A file system implementation. In Node.js, use promises as fs from the node:fs module.The object must have a readdir(path: string): Promise<string[]> method.
path
FileMigrationProviderPath
required
A path utility implementation. In Node.js, use the path module.The object must have a join(...path: string[]): string method.
The absolute or relative path to the folder containing migration files.
Example
import { promises as fs } from 'node:fs'
import path from 'node:path'
import { FileMigrationProvider } from 'kysely'
const provider = new FileMigrationProvider({
fs,
path,
migrationFolder: 'path/to/migrations/folder'
})
Complete Example with Migrator
import { promises as fs } from 'node:fs'
import path from 'node:path'
import { Kysely, Migrator, FileMigrationProvider, SqliteDialect } from 'kysely'
import * as Sqlite from 'better-sqlite3'
const db = new Kysely<any>({
dialect: new SqliteDialect({
database: Sqlite(':memory:')
})
})
const migrator = new Migrator({
db,
provider: new FileMigrationProvider({
fs,
path,
migrationFolder: './migrations',
})
})
const { error, results } = await migrator.migrateToLatest()
if (error) {
console.error('Migration failed')
console.error(error)
process.exit(1)
}
console.log('Migrations completed successfully')
Migration files should export a Migration object with up and optionally down methods:
// migrations/001_create_users.ts
import { Kysely } from 'kysely'
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('users')
.addColumn('id', 'serial', (col) => col.primaryKey())
.addColumn('name', 'varchar(255)', (col) => col.notNull())
.addColumn('email', 'varchar(255)', (col) => col.notNull().unique())
.execute()
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('users').execute()
}
Alternatively, you can use default exports:
// migrations/002_add_posts.ts
import { Kysely, Migration } from 'kysely'
const migration: Migration = {
async up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('posts')
.addColumn('id', 'serial', (col) => col.primaryKey())
.addColumn('title', 'varchar(255)', (col) => col.notNull())
.addColumn('user_id', 'integer', (col) =>
col.references('users.id').onDelete('cascade').notNull()
)
.execute()
},
async down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('posts').execute()
}
}
export default migration
How It Works
- File Discovery: The provider reads all files from the specified
migrationFolder
- File Filtering: Only files with valid extensions (
.js, .ts, .mjs, .mts) are processed
- Dynamic Import: Each file is dynamically imported using
import()
- Migration Extraction: The provider handles both default exports and named exports
- Key Generation: Migration keys are derived from filenames (without extension)
Naming Conventions
Migration files should follow a naming convention that ensures proper ordering:
001_initial_schema.ts
002_add_users_table.ts
003_add_posts_table.ts
2024_01_15_create_comments.ts
The migration name (key) is the filename without the extension. Kysely sorts migrations alphabetically by these keys.
Helper Types
FileMigrationProviderFS
Interface for the file system dependency.
interface FileMigrationProviderFS {
readdir(path: string): Promise<string[]>
}
In Node.js, you can use:
import { promises as fs } from 'node:fs'
FileMigrationProviderPath
Interface for the path utility dependency.
interface FileMigrationProviderPath {
join(...path: string[]): string
}
In Node.js, you can use:
import path from 'node:path'
Custom Migration Providers
You can create custom migration providers for different storage mechanisms (databases, remote APIs, etc.):
import { MigrationProvider, Migration } from 'kysely'
class DatabaseMigrationProvider implements MigrationProvider {
constructor(private configDb: Kysely<any>) {}
async getMigrations(): Promise<Record<string, Migration>> {
const migrations = await this.configDb
.selectFrom('stored_migrations')
.select(['name', 'up_sql', 'down_sql'])
.execute()
const result: Record<string, Migration> = {}
for (const migration of migrations) {
result[migration.name] = {
async up(db) {
await db.executeQuery(migration.up_sql)
},
async down(db) {
if (migration.down_sql) {
await db.executeQuery(migration.down_sql)
}
}
}
}
return result
}
}
Best Practices
- Consistent Naming: Use a consistent naming scheme for migration files (e.g., timestamp prefix)
- One Change Per Migration: Keep migrations focused on a single logical change
- Provide Down Methods: Always implement
down() methods when possible for rollback capability
- Test Migrations: Test both
up() and down() methods before deploying
- Idempotent Operations: Make migrations safe to retry (use
IF NOT EXISTS, etc.)
- Version Control: Always commit migration files to version control