A Nuxt module aimed to simplify the use of PGlite.
PGlite, an Embeddable Postgres Run a full Postgres database locally in WASM with reactivity and live sync.
Warning
No docs are available (although planned), please refer to the playground code.
- ⚡️ Server-side
usePGlite
, running in your Node or Bun server. - 🧑💻 Client-side
usePGlite
, running inside Web Workers. - 🪢 Client-side
useLiveQuery
anduseLiveIncrementalQuery
to subscribe to live changes.
Install the module to your Nuxt application with one command:
npx nuxi module add nuxt-pglite
That's it! You can now use Nuxt PGlite in your Nuxt app ✨
You can configure where to store data in your nuxt.config.ts
. Server-side storage accepts relative baths:
export default defineNuxtConfig({
modules: ['nuxt-pglite'],
pglite: {
client: {
options: {
dataDir: 'idb://nuxt-pglite',
},
},
server: {
options: {
dataDir: './database/pglite', // will use `~~/server/database/pglite`
},
},
},
})
For supported filesystem please refer to the official documentation.
Extensions are automatically configured with full type support and can be added via nuxt.config.ts
:
export default defineNuxtConfig({
modules: ['nuxt-pglite'],
pglite: {
client: {
extensions: ['live', 'electricSync'],
},
},
})
For a full list of available extensions please refer to the official docs. If a new extension is missing feel free to open up a new PR by adding it to this file. I do plan to support only official and contrib extensions.
Warning
Auto configuration for server-side extensions will be supported once Nuxt v3.15
gets released. See below how to use hooks to add extensions server-side.
With Live Queries we can subscrive to events happening in the database and reactively update the user interface. This becomes particularly usefuly client-side thanks to Web Workers, allowing us to keep content in sync even when the user opens up multiple tabs.
To get started simply add live
extension to your nuxt.config.ts
:
export default defineNuxtConfig({
modules: ['nuxt-pglite'],
pglite: {
client: {
extensions: [
// ...
'live',
],
},
},
})
This will enable auto-import for useLiveQuery
and useLiveIncrementalQuery
. The quick implementation would be:
<script setup lang="ts">
const maxNumber = ref(100)
const items = useLiveQuery.sql`
SELECT *
FROM my_table
WHERE number <= ${maxNumber.value}
ORDER BY number;
`
</script>
Live queries are currently a port of the upstream implementation, you can read more here.
We can use hooks to customize or extend PGlite at runtime. This becomes particularly useful in conjunction with RLS
or adding custom extensions server-side.
PGlite supports RLS out of the box, but being a single-user/single-connection database it is more frequent to be used only client side. Lets take in example a basic implementation with nuxt-auth-utils
. We'll need to create a client-only Nuxt plugin /plugins/rls.client.ts
:
export default defineNuxtPlugin((nuxtApp) => {
const { user } = useUserSession()
if (user) {
nuxtApp.hook('pglite:config', (options) => {
options.username = user.id
})
}
})
This, in combination with Sync
, will make us able to create an offline-first application with the ability for the users to save their data in a centralized postgres instance.
We can also use hooks to pass custom options to extensions like Sync
as well as improve typing for the whole project.
In the following example we are creating a /server/plugins/extend-pglite.ts
plugin that adds and configure pgvector
and Sync
:
import { vector } from '@electric-sql/pglite/vector'
import { electricSync } from '@electric-sql/pglite-sync'
import { pgliteHooks } from '#pglite-utils'
export default defineNitroPlugin(() => {
pgliteHooks.hook('pglite:config', (options) => {
options.extensions = {
vector,
electric: electricSync({
metadataSchema: 'my-electric',
}),
}
})
pgliteHooks.hookOnce('pglite', async (pg) => {
await pg.query('CREATE EXTENSION IF NOT EXISTS vector;')
})
})
declare module '#pglite-utils' {
interface PGliteServerExtensions {
vector: typeof vector
electric: ReturnType<typeof electricSync>
}
}
Warning
Until Nuxt v3.15
gets released this is the only way to add extensions server-side.
A few things to consider are that:
- we rely on
nuxtApp
hooks for client-side, whilepgliteHooks
imported from#pglite-utils
for server-side, hooks available are:pglite:config
: provides access toPGliteOptions
before initializing a new PGlite instance.pglite
: called on every PGlite execution.
- To improve types when manually adding extensions we use
PGliteClientExtensions
andPGliteServerExtensions
for client and server respectively.
Any ORM that accept a PGlite instance should be supported both server and client side.
Drizzle integration is as simple as:
import { drizzle } from 'drizzle-orm/pglite'
import * as schema from '../my-path-to/schema'
export function useDB() {
return drizzle(usePGlite(), { schema })
}
Local development
# Install dependencies
pnpm install
# Generate type stubs
pnpm run dev:prepare
# Develop with the playground
pnpm run dev
# Build the playground
pnpm run dev:build
# Run ESLint
pnpm run lint
# Run Vitest
pnpm run test
pnpm run test:watch
# Release new version
pnpm run release
Published under the MIT license.