From c250e73e0018186a8867fb2ffb38cad4c8b38dd6 Mon Sep 17 00:00:00 2001 From: Adam Kasper Date: Sun, 14 Dec 2025 08:59:11 +0100 Subject: [PATCH 1/4] feat(db): add `drizzle-orm/neon-http` support --- docs/content/docs/2.features/0.database.md | 32 ++++++++++++++ src/db/lib/client.ts | 7 ++++ src/db/setup.ts | 18 ++++++++ src/types/config.ts | 6 +-- test/database.config.test.ts | 49 ++++++++++++++++++++++ 5 files changed, 109 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/2.features/0.database.md b/docs/content/docs/2.features/0.database.md index bc3bf62e..f2197ff3 100644 --- a/docs/content/docs/2.features/0.database.md +++ b/docs/content/docs/2.features/0.database.md @@ -21,6 +21,7 @@ Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database NuxtHub automatically detects your database connection using environment variables: - Uses `PGlite` (embedded PostgreSQL) if no environment variables are set. - Uses `postgres-js` driver if you set `DATABASE_URL`, `POSTGRES_URL`, or `POSTGRESQL_URL` environment variable. + - Use `neon-http` driver with `@neondatabase/serverless` for [Neon](https://neon.com) serverless PostgreSQL. :: ::: :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} @@ -780,6 +781,37 @@ This driver requires the following environment variables: You can find your Cloudflare account ID and create API tokens in the [Cloudflare dashboard](https://dash.cloudflare.com). The API token needs `D1:Edit` permissions. :: +### Neon Serverless + +Use the `neon-http` driver to connect to [Neon](https://neon.com) serverless PostgreSQL. This driver uses HTTP protocol optimized for serverless environments. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { + db: { + dialect: 'postgresql', + driver: 'neon-http' + } + } +}) +``` + +Install the required dependency: + +```bash +pnpm add @neondatabase/serverless +``` + +This driver requires the following environment variable: + +| Variable | Description | +| --- | --- | +| `DATABASE_URL` | Your Neon database connection string (or `POSTGRES_URL` / `POSTGRESQL_URL`) | + +::callout{icon="i-lucide-info"} +You can find your Neon connection string in the [Neon dashboard](https://console.neon.tech). The connection string format is `postgresql://user:password@hostname/database`. +:: + ## Migration guide ::important diff --git a/src/db/lib/client.ts b/src/db/lib/client.ts index 7a319679..067287b1 100644 --- a/src/db/lib/client.ts +++ b/src/db/lib/client.ts @@ -19,6 +19,13 @@ export async function createDrizzleClient(config: ResolvedDatabaseConfig, hubDir pkg = 'drizzle-orm/postgres-js' const { drizzle } = await import(pkg) return drizzle({ client }) + } else if (driver === 'neon-http') { + const clientPkg = '@neondatabase/serverless' + const { neon } = await import(clientPkg) + const sql = neon(connection.url) + pkg = 'drizzle-orm/neon-http' + const { drizzle } = await import(pkg) + return drizzle(sql) } else if (driver === 'libsql') { pkg = 'drizzle-orm/libsql' } else if (driver === 'mysql2') { diff --git a/src/db/setup.ts b/src/db/setup.ts index 9d503f84..5ce7a372 100644 --- a/src/db/setup.ts +++ b/src/db/setup.ts @@ -61,6 +61,13 @@ export async function resolveDatabaseConfig(nuxt: Nuxt, hub: HubConfig): Promise } case 'postgresql': { config.connection = defu(config.connection, { url: process.env.POSTGRES_URL || process.env.POSTGRESQL_URL || process.env.DATABASE_URL || '' }) + // Neon HTTP + if (config.driver === 'neon-http') { + if (!config.connection.url) { + throw new Error('Neon HTTP driver requires DATABASE_URL, POSTGRES_URL, or POSTGRESQL_URL environment variable') + } + break + } if (config.connection.url) { config.driver ||= 'postgres-js' break @@ -103,6 +110,8 @@ export async function setupDatabase(nuxt: Nuxt, hub: HubConfig, deps: Record { driver: 'custom-postgres' }) }) + + it('should use neon-http driver when explicitly set', async () => { + process.env.DATABASE_URL = 'postgresql://user:pass@localhost:5432/db' + + const nuxt = createMockNuxt() + const hub = createBaseHubConfig({ + dialect: 'postgresql', + driver: 'neon-http' + }) + + const result = await resolveDatabaseConfig(nuxt, hub) + + expect(result).toMatchObject({ + dialect: 'postgresql', + driver: 'neon-http', + connection: { + url: 'postgresql://user:pass@localhost:5432/db' + } + }) + }) + + it('should not use neon-http driver automatically', async () => { + process.env.DATABASE_URL = 'postgresql://user:pass@localhost:5432/db' + + const nuxt = createMockNuxt() + const hub = createBaseHubConfig('postgresql') + + const result = await resolveDatabaseConfig(nuxt, hub) + + expect(result).toMatchObject({ + dialect: 'postgresql', + driver: 'postgres-js', + connection: { + url: 'postgresql://user:pass@localhost:5432/db' + } + }) + }) + + it('should throw error when DATABASE_URL is not set for neon-http', async () => { + const nuxt = createMockNuxt() + const hub = createBaseHubConfig({ + dialect: 'postgresql', + driver: 'neon-http' + }) + + await expect(resolveDatabaseConfig(nuxt, hub)).rejects.toThrow( + 'Neon HTTP driver requires DATABASE_URL, POSTGRES_URL, or POSTGRESQL_URL environment variable' + ) + }) }) describe('MySQL dialect', () => { From ff00e60475dc703341a78278dc3a97c3b69b1623 Mon Sep 17 00:00:00 2001 From: Adam Kasper Date: Sun, 14 Dec 2025 11:21:29 +0100 Subject: [PATCH 2/4] fix(db): update connection URL handling in setup --- src/db/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/setup.ts b/src/db/setup.ts index 5ce7a372..eb531b82 100644 --- a/src/db/setup.ts +++ b/src/db/setup.ts @@ -282,7 +282,7 @@ export { db, schema } import { drizzle } from 'drizzle-orm/neon-http' import * as schema from './db/schema.mjs' -const sql = neon(${JSON.stringify(connection.url)}) +const sql = neon(${connection.url}) const db = drizzle(sql, { schema }) export { db, schema } ` From 52b26d3e174f178229a23c478106443de99c0157 Mon Sep 17 00:00:00 2001 From: Adam Kasper Date: Sun, 14 Dec 2025 11:32:39 +0100 Subject: [PATCH 3/4] fix(db): correct connection string variable in setup --- src/db/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/setup.ts b/src/db/setup.ts index eb531b82..381110e9 100644 --- a/src/db/setup.ts +++ b/src/db/setup.ts @@ -282,7 +282,7 @@ export { db, schema } import { drizzle } from 'drizzle-orm/neon-http' import * as schema from './db/schema.mjs' -const sql = neon(${connection.url}) +const sql = neon(${connection.connectionString}) const db = drizzle(sql, { schema }) export { db, schema } ` From f7d13b9b3dc235f5dd99a9696a888ca30182442a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Mon, 15 Dec 2025 17:51:00 +0100 Subject: [PATCH 4/4] chore: small improvement --- src/db/setup.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/db/setup.ts b/src/db/setup.ts index 381110e9..95e7e2b2 100644 --- a/src/db/setup.ts +++ b/src/db/setup.ts @@ -61,12 +61,8 @@ export async function resolveDatabaseConfig(nuxt: Nuxt, hub: HubConfig): Promise } case 'postgresql': { config.connection = defu(config.connection, { url: process.env.POSTGRES_URL || process.env.POSTGRESQL_URL || process.env.DATABASE_URL || '' }) - // Neon HTTP - if (config.driver === 'neon-http') { - if (!config.connection.url) { - throw new Error('Neon HTTP driver requires DATABASE_URL, POSTGRES_URL, or POSTGRESQL_URL environment variable') - } - break + if (config.driver && ['neon-http', 'postgres-js'].includes(config.driver) && !config.connection.url) { + throw new Error(`\`${config.driver}\` driver requires \`DATABASE_URL\`, \`POSTGRES_URL\`, or \`POSTGRESQL_URL\` environment variable`) } if (config.connection.url) { config.driver ||= 'postgres-js'