Development socket

PGlite in dev, Postgres in prod

Develop against PGlite with nothing to install, deploy against any Postgres, with the same code and no PGlite in the build.

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:

nuxt.config.ts
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 sets DATABASE_URL to its own URL.
  • In production, DATABASE_URL comes from your host, and the build contains your driver only.

Your database code reads DATABASE_URL and nothing else.

Read DATABASE_URL

server/utils/db.ts
import { drizzle } from 'drizzle-orm/node-postgres'

import { relations } from '../database/relations'

export function useDB() {
  return drizzle(process.env.DATABASE_URL!, { relations })
}
server/database/schema.ts
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(),
})
server/database/relations.ts
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:

server/pglite.config.ts
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),
})
drizzle.config.ts
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',
  },
})
Terminal
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

server/api/visits.post.ts
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 devProduction build
DATABASE_URL (or the provider's variables)the socket's URL, unless already setyour host's value
server/pglite.config.tsloaded by the socketnot bundled
@electric-sql/pglite in the server outputn/aabsent
usePGlite() / #pglite/servera stub that rejects with a pointera stub that rejects with a pointer
The socket imports the server config file in the Nuxt process, outside any bundle. 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.