PGlite in dev, Postgres in prod
The most common setup: PGlite replaces the local Postgres you would otherwise install, run or containerise, while production uses a real database. The socket does not depend on the server side, so you can disable the latter:
export default defineNuxtConfig({
modules: ['nuxt-pglite'],
pglite: {
server: {
enabled: false, // no `usePGlite()`, nothing of PGlite in the build
},
socket: { port: 5433 }, // but a Postgres URL while `nuxt dev` runs
},
})
- In
nuxt dev, the socket starts and setsDATABASE_URLto its own URL. - In production,
DATABASE_URLcomes from your host, and the build contains your driver only.
Your database code reads DATABASE_URL and nothing else.
Read DATABASE_URL
import { drizzle } from 'drizzle-orm/node-postgres'
import { relations } from '../database/relations'
export function useDB() {
return drizzle(process.env.DATABASE_URL!, { relations })
}
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'
// Enables `db.query`, even with no relations to declare.
export const relations = defineRelations(schema)
Prepare the development database
The socket still reads server/pglite.config.ts, so init, extensions and per-environment settings apply to the development database. Generate migrations from the schema with drizzle-kit generate, and let init apply them, so that the local database is built from the same files as the production one:
import { fileURLToPath } from 'node:url'
import { applyMigrations } from 'nuxt-pglite/migrations'
const migrations = fileURLToPath(new URL('./database/migrations', import.meta.url))
export default definePGliteServerConfig({
init: (pg) => applyMigrations(pg, migrations),
})
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './server/database/schema.ts',
out: './server/database/migrations',
dialect: 'postgresql',
dbCredentials: {
// the socket, for `drizzle-kit studio` and `check` while `nuxt dev` runs
url: 'postgres://postgres@127.0.0.1:5433/postgres',
},
})
npx drizzle-kit generate
init runs when the socket's instance is created, as nuxt dev starts: restart it after generating a migration. See Migrations for the details. Without migration files, plain SQL in init (CREATE TABLE IF NOT EXISTS …) does the same job.
Use it in routes
import { count } from 'drizzle-orm'
import { visits } from '../database/schema'
export default defineEventHandler(async () => {
const db = useDB()
await db.insert(visits).values({})
const [{ total }] = await db.select({ total: count() }).from(visits)
return { total }
})
Deploy with a real DATABASE_URL
Set DATABASE_URL in your host's environment to any Postgres: Supabase, Railway, Neon, a managed instance or your own server. Apply the same migration files to it, with drizzle-kit migrate (or your tool's migrate) in your build command or CI, or let a platform that applies them itself do it. See Deploy for both.
This site runs this setup on Netlify: the Netlify recipe is a deployed example.
What ends up where
nuxt dev | Production build | |
|---|---|---|
DATABASE_URL (or the provider's variables) | the socket's URL, unless already set | your host's value |
server/pglite.config.ts | loaded by the socket | not bundled |
@electric-sql/pglite in the server output | n/a | absent |
usePGlite() / #pglite/server | a stub that rejects with a pointer | a stub that rejects with a pointer |
definePGliteServerConfig is provided there even with the server side disabled, and the app's aliases (~~, #pglite/migrations, …) resolve as they do in the server bundle; auto-imports other than the config helper do not.