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.
usePGlite, running in your Node, Bun or Deno servers.usePGlite, running inside Web Workers.useLiveQuery and useLiveIncrementalQuery 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 based on rootDir (~~):
export default defineNuxtConfig({
modules: ['nuxt-pglite'],
pglite: {
client: {
options: {
dataDir: 'idb://nuxt-pglite',
},
},
server: {
options: {
dataDir: '.data/pglite', // will use `~~/.data/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.
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 custom fork of the upstream implementation, which 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'
export default defineNitroPlugin((nitro) => {
nitro.hooks.hook('pglite:config', (options) => {
options.extensions = {
vector,
electric: electricSync({
metadataSchema: 'my-electric',
}),
}
})
nitro.hooks.hook('pglite:init', async (pg) => {
await pg.query('CREATE EXTENSION IF NOT EXISTS vector;')
})
})
// Improve typing for server-side extensions
declare module '#pglite-utils' {
interface PGliteServerExtensions {
vector: typeof vector
electric: ReturnType<typeof electricSync>
}
}
!WARNING
This is currently the only way to type server-side extensions.
A few things to consider are that:
nuxtApp hooks for client-side, while nitroApp for server-side, hooks available are:
pglite:config: provides access to PGliteOptions before initializing a new PGlite instance.pglite:init: provides access to the initialized PGlite instance.PGliteClientExtensions and PGliteServerExtensions for client and server respectively.Any ORM that accept a PGlite or PGliteWorker instances should be supported both server and client side.
Drizzle integration for server-side is as simple as:
import { drizzle } from 'drizzle-orm/pglite'
import * as schema from '../my-path-to/schema'
export function useDB() {
const pg = await usePGlite()
return drizzle(pg, { schema })
}
# 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.