Netlify
Netlify Database is a platform that applies the migrations itself, and sets its own variable rather than DATABASE_URL. This documentation site runs on it, so it is the worked example: the PGlite in dev, Postgres in prod setup, with the socket's netlify preset standing in for Netlify Database in development, plus an in-browser instance for the examples:
export default defineNuxtConfig({
modules: ['nuxt-pglite'],
pglite: {
client: { enabled: true }, // the interactive examples
server: {
enabled: false, // nothing of PGlite on the server
},
socket: { port: 5456, provider: 'netlify' }, // `NETLIFY_DB_URL` while `nuxt dev` runs
},
})
How it fits together
Netlify Database is a Postgres that Netlify provisions for the site. It sets NETLIFY_DB_URL in builds and functions (never DATABASE_URL), and applies the SQL files in netlify/database/migrations itself, right before it publishes a deploy. Each deploy preview gets its own database branch, with the migrations of that deploy applied to it.
nuxt dev | Deployed on Netlify | |
|---|---|---|
NETLIFY_DB_URL | the socket's URL, from the netlify preset | the site's database (or the preview's branch) |
NETLIFY_DB_DRIVER | server: pg over the wire protocol | Netlify's default: Neon's serverless driver |
netlify/database/migrations | applied by init with applyMigrations | applied by Netlify before publishing |
| Applied migrations recorded in | netlify.migrations, applyMigrations's default | netlify.migrations |
The routes only know drizzle-orm/netlify-db. Nothing else changes between development and production.
Try it
The demo below calls /api/visits, a route that uses Drizzle over NETLIFY_DB_URL. In nuxt dev the socket exports that variable and the route reaches PGlite; on the deployed site Netlify sets it, and the route tells you when the database is not enabled.
What ends up in the build
| Server output | Client output | |
|---|---|---|
| PGlite engine and WebAssembly | absent (server.enabled: false) | present, loaded in a worker on first use |
server/pglite.config.ts | absent: only the socket reads it, in nuxt dev | absent |
app/pglite.config.ts | absent | present |
Your driver (drizzle-orm/netlify-db, pg, @neondatabase/serverless) | present | absent |
Steps
Write the schema and generate migrations
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core'
export const visits = pgTable('visits', {
id: serial('id').primaryKey(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
import { defineRelations } from 'drizzle-orm'
import * as schema from './schema'
export const relations = defineRelations(schema)
drizzle-kit 1.0.0-rc.4 or later writes its migrations where Netlify looks for them:
import { defineConfig } from 'drizzle-kit'
// Netlify applies `out` on deploy and `init` applies it locally: no `push`/`migrate`.
export default defineConfig({
out: './netlify/database/migrations',
schema: './server/database/schema.ts',
dialect: 'postgresql',
dbCredentials: {
// the development socket, for `studio` and `check` while `nuxt dev` runs
url: 'postgres://postgres@127.0.0.1:5456/postgres',
},
})
npx drizzle-kit generate --name init
Commit the generated netlify/database/migrations/<timestamp>_init/ directory: the deploy is what applies it.
drizzle-kit push or drizzle-kit migrate against the site's database. Netlify records the files it applied in netlify.migrations; a schema pushed or migrated by another tool is one it does not know about, and the next deploy's migrations run against it.Apply them in development
The socket's instance applies the same files from init, with the same bookkeeping as Netlify:
import { fileURLToPath } from 'node:url'
import { applyMigrations } from 'nuxt-pglite/migrations'
const migrations = fileURLToPath(new URL('../netlify/database/migrations', import.meta.url))
export default definePGliteServerConfig({
init: (pg) => applyMigrations(pg, migrations),
})
Query through drizzle-orm/netlify-db
Called with no connection, drizzle() from drizzle-orm/netlify-db reads NETLIFY_DB_URL, and picks pg when NETLIFY_DB_DRIVER is server, which is what the socket exports, or Neon's serverless driver otherwise, which is what Netlify runs. It needs pg and @neondatabase/serverless installed, and throws when NETLIFY_DB_URL is unset, so this site checks first:
import { drizzle } from 'drizzle-orm/netlify-db'
import { relations } from '../database/relations'
function createDB() {
return drizzle({ relations })
}
let db: ReturnType<typeof createDB> | undefined
/** `undefined` without `NETLIFY_DB_URL`, so the routes can say so. */
export function useDB() {
if (!process.env.NETLIFY_DB_URL) {
return undefined
}
db ??= createDB()
return db
}
Add a netlify.toml
Netlify detects Nitro, which picks its netlify preset by itself. In a monorepo, point the build at the app's directory:
# Netlify applies `netlify/database/migrations` itself before publishing: no migration command.
[build]
base = "docs"
command = "pnpm build"
publish = "dist"
[build.environment]
NODE_VERSION = "24"
publish is relative to base: the netlify preset writes the static files to dist and the server to .netlify/functions-internal. For an app at the root of its repository, drop base.
Enable Netlify Database on the site
Netlify provisions the database for sites that depend on @netlify/database; otherwise, enable it from the site's Data & Storage → Database page in the Netlify UI. Once enabled, NETLIFY_DB_URL is set for builds and functions with nothing to configure.
Deploy
Push to the branch Netlify builds, or run netlify deploy --build. Netlify applies the pending migrations before publishing, and a failing one blocks the publish.
Without the database enabled, the site still deploys and every page works; the routes that need the database answer with a "no database configured" message naming NETLIFY_DB_URL. The demo above shows which case this deployment is in.
applyMigrations does the same, so the mistake shows up in nuxt dev rather than in a failed deploy.