Connect to Postgres local vs Neon with Kysely
By Flavio Copes
Set up Kysely with a local Postgres database using the pg Pool, then connect to Neon with kysely-neon for serverless or PostgresDialect when you need transactions.
I switched a codebase from a local Postgres database to a hosted one on Neon. Vercel Postgres used to be the hosted option in that stack. Vercel retired it in late 2024 and moved Marketplace Postgres onto Neon. The local path stayed the same. The hosted path changed packages.
For the local database, Kysely sits on top of the standard pg driver. You give it a dialect wrapping a connection pool:
import {
Kysely,
PostgresDialect,
} from 'kysely'
import pg from 'pg'
const POSTGRES_URL = process.env.POSTGRES_URL
const dialect = new PostgresDialect({
pool: new pg.Pool({
connectionString: POSTGRES_URL,
max: 10,
}),
})
export const db = new Kysely({
dialect
})
The connection string is a normal Postgres URL:
postgresql://user:password@localhost:5432/mydb
Keep it in an environment variable, out of the repository. max: 10 caps how many connections this process can hold, which matters once several instances of the app share one database.
Neon on serverless or edge
On Vercel (or any serverless / edge runtime), use Neon’s HTTP driver with kysely-neon and @neondatabase/serverless:
import { Kysely } from 'kysely'
import { NeonDialect } from 'kysely-neon'
import { neon } from '@neondatabase/serverless'
export const db = new Kysely({
dialect: new NeonDialect({
neon: neon(process.env.DATABASE_URL),
}),
})
Vercel sets DATABASE_URL (or POSTGRES_URL, depending on how you linked the store) when you attach a Marketplace Postgres / Neon database to the project. Old projects that used @vercel/postgres-kysely should move to this Neon stack.
The HTTP dialect is great for short queries. It does not give you full Postgres transactions over WebSockets. When you need transactions, use Neon’s WebSocket Pool with Kysely’s normal PostgresDialect:
import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from '@neondatabase/serverless'
export const db = new Kysely({
dialect: new PostgresDialect({
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
}),
})
For more hosting options beyond Neon, see where to host a PostgreSQL database.
Everything downstream of db stays identical in both setups. That is the point of Kysely’s dialect layer: queries like
const rows = await db
.selectFrom('notes')
.select(['id', 'title'])
.execute()
do not change when the database moves.
To verify which database you are actually talking to, run a quick check at startup:
const result = await sql`select current_database()`.execute(db)
console.log(result.rows)
(sql comes from kysely.) I did this after the switch because the classic failure mode here is an environment variable pointing at the old local database in one environment and at Neon in another — everything works, just against the wrong data. If the printed database name is not what you expect, fix the environment before debugging anything else.
Want me to talk about your product? You can sponsor this site.
Related posts about database: