Migrations
A migration history is a folder of SQL files. In development, init applies them to PGlite with applyMigrations from nuxt-pglite/migrations, so the local database is built from the same files as the production one. In production, either you apply them (from the build command or a CI job), or the platform does, from the files committed with your app.
import { fileURLToPath } from 'node:url'
import { applyMigrations } from 'nuxt-pglite/migrations'
// Absolute, so that it does not depend on where `nuxt dev` is started from.
const migrations = fileURLToPath(new URL('./database/migrations', import.meta.url))
export default definePGliteServerConfig({
init: (pg) => applyMigrations(pg, migrations),
})
init runs once per created instance, the socket's included, so the database is brought up to the files before anything queries it. Each migration runs in its own transaction together with its tracking row, and applyMigrations resolves with the names it applied. Add a migration, restart nuxt dev, and it is applied.
A relative directory is resolved from process.cwd(), which is why the example builds an absolute one from the config file's location. With the in-process server (server.enabled), init also runs in the built app, where the files are not shipped: keep it under $development there, or pass a directory that exists at runtime.
#pglite/migrations imports the same entry as nuxt-pglite/migrations.Generating the files
Any tool that writes plain SQL files works. A migration is either a <name>/migration.sql directory or a flat <name>.sql file; anything else in the directory is ignored, and migrations apply in name order.
drizzle-kit 1.0.0-rc.4 and later writes <timestamp>_<name>/migration.sql next to a snapshot.json:
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './server/database/schema.ts',
out: './server/database/migrations',
dialect: 'postgresql',
dbCredentials: {
// the development socket, for `studio` and `check` while `nuxt dev` runs
url: 'postgres://postgres@127.0.0.1:5433/postgres',
},
})
npx drizzle-kit generate --name add_visits
20261008032022_init/
migration.sql
snapshot.json
20261012093000_add_visits/
migration.sql
snapshot.json
Against the development socket, prefer the files to drizzle-kit push: a pushed schema is not what production will be built from.
In production
The same files reach the real database in one of two ways, depending on your platform. See Deploy for how each fits with the rest of the setup.
- You apply them: most hosts only run your build. Run your tool's migrate command with the production
DATABASE_URL, in the build command or a CI job, before the new code goes live:drizzle-kit migrate, orapplyMigrationsitself, below. See Any Postgres. - The platform applies them: some platforms apply the SQL files committed with your app on deploy. Point
outat the directory they read, and never runpushormigrateagainst their database: the platform records what it applied, and a schema changed behind its back is one the next deploy's migrations run against blindly. See Netlify.
Applying them yourself
Any database with exec, query and transaction works, PGlite as is. fromPool adapts a pg pool, so the files init applies locally are applied to Postgres by the same code, with the same bookkeeping:
import { Pool } from 'pg'
import { applyMigrations, fromPool } from 'nuxt-pglite/migrations'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
try {
console.log(await applyMigrations(fromPool(pool), 'server/database/migrations'))
} finally {
await pool.end()
}
Concurrent appliers on the same database (two dev processes, a CI job and a deploy) take turns behind an advisory lock. The entry depends on Node built-ins only.
The tracking table
Applied migrations are recorded in netlify.migrations by default. That is Netlify's bookkeeping, so that a Netlify deploy and the local database agree on what was applied; any other platform just sees a tracking table. table renames it:
export default definePGliteServerConfig({
init: (pg) => applyMigrations(pg, migrations, { table: 'public.schema_migrations' }),
})
A tool with its own bookkeeping, such as drizzle-kit migrate (drizzle.__drizzle_migrations), keeps it separately: each database is tracked by the tool that migrates it, from the same files.
Options
await applyMigrations(pg, migrations, {
table: 'netlify.migrations', // the tracking table, `schema.table` or `table`
digests: true, // the drift check, see Edited migrations; a string names another table
target: '20261008032022', // stop at this migration, by full name or by the part before an `_`
logger: console, // anything with `info` and `warn`; silent by default
})
readMigrations(dir) lists the migrations as applyMigrations sees them, for tooling.
A failing migration throws a MigrationError, with the migration name and the database error as cause, once its transaction is rolled back; the migrations after it are not run. Thrown from init, it leaves the socket refusing clients with that message until the cause is fixed.
Testing a migrated database
A test database from nuxt-pglite/testing runs the config's init, so the migrations are applied to it as they are to the development one, and tests run against the schema the files produce. Build the directory from the config file's location, as above: a test runner's working directory is not always the app's. The config file is loaded outside Nuxt there, where #pglite/migrations needs alias; importing from nuxt-pglite/migrations works in both places.
To start each test from the migrated schema, apply them once and fork per test, rather than creating a database per test.