nuxt-pg
v0.2.0
Published
Seamless Nuxt 'pg' integration with event context-aware transaction handling.
Maintainers
Readme
nuxt-pg
PostgreSQL database module for Nuxt with automatic transaction management
nuxt-pg provides a clean, type-safe PostgreSQL integration for Nuxt with automatic transaction context tracking. Write cleaner code by eliminating transaction parameter passing throughout your application.
Features
- Automatic Transaction Context - No need to pass transaction objects through function calls
- Lazy Initialization - Database initializes on first
getPg()call, giving you full control over startup order - DevTools Integration - Switch between database connections in Nuxt DevTools during development
- Connection Hooks - Listen for connection switch events via Nitro hooks
- Type-Safe - Full TypeScript support with proper type inference
- Connection Pooling - Built-in connection pool management with
node-postgres - Flexible Pool Configuration - Customize pool settings globally or per-connection
- Request Isolation - Transactions are automatically scoped per-request
- Graceful Shutdown - Properly closes connections on server shutdown
Installation
npm install nuxt-pg pgQuick Start
1. Add to your nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-pg'],
nuxtPg: {
connectionString: process.env.DATABASE_URL || '',
},
})2. Use in your API routes
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const db = getPg()
const id = getRouterParam(event, 'id')
const users = await db.query(
'SELECT * FROM users WHERE id = $1',
[id]
)
return users[0]
})getPg() is auto-imported in all server code. The first call initializes the database connection; subsequent calls return the existing singleton.
Usage
Basic Queries
const db = getPg()
// Simple query
const users = await db.query('SELECT * FROM users')
// With parameters
const user = await db.query(
'SELECT * FROM users WHERE email = $1',
['[email protected]']
)
// With TypeScript types
interface User {
id: string
name: string
email: string
}
const users = await db.query<User>(
'SELECT * FROM users WHERE active = $1',
[true]
)Transactions
Automatic (Recommended)
export default defineEventHandler(async (event) => {
const db = getPg()
return await db.transactional(async (txn) => {
await txn.query('INSERT INTO users (name) VALUES ($1)', ['Alice'])
await txn.query('INSERT INTO profiles (user_id) VALUES ($1)', [userId])
// Automatically commits on success, rolls back on error
return { success: true }
})
})Manual Control
export default defineEventHandler(async (event) => {
const db = getPg()
await db.createTransaction()
try {
await db.query('INSERT INTO users ...')
await db.query('UPDATE profiles ...')
await db.commitTransaction()
return { success: true }
} catch (error) {
await db.rollbackTransaction()
throw error
}
})Context-Aware Queries
Transactions are tracked per-request — no need to pass transaction objects through your call stack:
// user.repo.ts
export class UserRepository {
static async create(data: CreateUserData) {
// Automatically uses the active transaction if one exists
return getPg().query('INSERT INTO users ...', [...])
}
}
// handler.ts
export default defineEventHandler(async (event) => {
const db = getPg()
return await db.transactional(async () => {
// Both use the same transaction automatically
await UserRepository.create({ name: 'Alice' })
await ProfileRepository.create(userId, { bio: 'Hello' })
})
})Configuration
Module Options
Configure nuxt-pg under the nuxtPg key in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-pg'],
nuxtPg: {
enabled: true, // default: true
connectionString: '', // production connection string
pool: { // global pool settings
maxConnections: 30,
minConnections: 2,
idleTimeoutMs: 30000,
connectionTimeoutMs: 2000,
ssl: true,
},
dev: { // dev-only settings
connections: { // named connections for switching
local: {
connectionString: process.env.DATABASE_URL_DEV || '',
pool: { ssl: false },
},
staging: {
connectionString: process.env.DATABASE_URL_STAGING || '',
},
},
defaultConnection: 'local', // which connection to use initially
},
devtools: { // devtools panel settings
apiUrl: '', // remote API URL (see DevTools section)
},
},
})When enabled is false, the module is entirely stripped — no aliases, runtime config, server imports, or hooks are registered. The only exception is the DevTools tab when devtools.apiUrl is set.
Pool Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| maxConnections | number | 30 | Maximum number of clients in the pool |
| minConnections | number | 2 | Minimum number of clients to maintain |
| idleTimeoutMs | number | 30000 | Time (ms) a client can be idle before being closed |
| connectionTimeoutMs | number | 2000 | Time (ms) to wait for a connection before timing out |
| ssl | boolean | true | Enable SSL connection |
Pool settings defined per-connection in dev.connections are merged with (and override) the global pool settings.
DevTools Integration
In dev mode, nuxt-pg adds a NuxtPG tab to Nuxt DevTools that lets you switch between named database connections on the fly.
Setup
Define multiple connections under dev.connections:
nuxtPg: {
connectionString: process.env.DATABASE_URL || '',
dev: {
connections: {
local: {
connectionString: process.env.DATABASE_URL_DEV || '',
pool: { ssl: false },
},
staging: {
connectionString: process.env.DATABASE_URL_STAGING || '',
},
production: {
connectionString: process.env.DATABASE_URL || '',
},
},
defaultConnection: 'local',
},
}The DevTools panel appears automatically. Click any connection to switch — the module waits for active transactions to complete, swaps the connection pool, and resumes.
Remote DevTools (Monorepo)
In a monorepo where multiple Nuxt apps share a single API project, you can show the connection switcher in all apps without initializing a database in each one:
// main-app/nuxt.config.ts, dashboard/nuxt.config.ts, etc.
export default defineNuxtConfig({
modules: ['nuxt-pg'],
nuxtPg: {
enabled: false,
devtools: {
apiUrl: 'http://localhost:3000', // your API project's dev URL
},
},
})With enabled: false and devtools.apiUrl set, the module only registers the DevTools tab pointing at the remote API — nothing else is loaded.
Connection Switch Hooks
When a database connection is switched (via DevTools or programmatically), nuxt-pg emits Nitro hooks you can listen to:
| Hook | Payload | When |
|------|---------|------|
| nuxtpg:connection:before-switch | { from, to } | Before the pool shutdown begins |
| nuxtpg:connection:switched | { from, to } | After a successful switch |
| nuxtpg:connection:switch-error | { from, to, error } | On switch failure |
Listening to Hooks
// server/plugins/on-db-switch.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('nuxtpg:connection:switched', ({ from, to }) => {
console.log(`Database switched from "${from}" to "${to}"`)
// Invalidate caches, re-seed data, notify services, etc.
})
nitroApp.hooks.hook('nuxtpg:connection:switch-error', ({ from, to, error }) => {
console.error(`Failed to switch from "${from}" to "${to}":`, error.message)
})
})Hook Payload Types
import type {
NuxtPgConnectionSwitchPayload,
NuxtPgConnectionSwitchErrorPayload,
NuxtPgHooks,
} from 'nuxt-pg'API Reference
getPg()
Returns the database service singleton. Auto-imported in all server code.
On first call, reads runtime config, initializes the connection pool, and registers a shutdown hook. Subsequent calls return the existing instance.
| Method | Description |
|--------|-------------|
| query<T>(sql, params?) | Execute a SQL query. Uses the active transaction if one exists. |
| createTransaction(timeoutMs?) | Start a new transaction in the current request context. |
| commitTransaction() | Commit the active transaction. |
| rollbackTransaction() | Rollback the active transaction. |
| transactional<T>(fn, timeoutMs?) | Execute a function within a transaction with automatic commit/rollback. |
| isHealthy() | Check if the connection pool is healthy. |
| shutdown() | Gracefully shutdown the database service. |
How It Works
nuxt-pg automatically enables Nitro's asyncContext to track database transactions across async boundaries. When you start a transaction, it's stored in the request context and automatically used by all subsequent queries in that request:
// Without nuxt-pg (manual transaction passing)
await userService.create(data, transaction)
await profileService.create(profileData, transaction)
// With nuxt-pg (automatic context tracking)
await userService.create(data)
await profileService.create(profileData)Requirements
- Nuxt 3.0+
- Node.js 18+
- PostgreSQL 12+
pgas a peer dependency
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
Credits
Built with:
