| name | neon |
| description | Neon serverless PostgreSQL: connection pooling, database branching, pgvector for AI, autoscaling patterns, Vercel integration |
Neon Skill
When to activate
- Setting up Neon DB in a serverless environment (Vercel, Cloudflare Workers, AWS Lambda)
- Using Neon database branching for preview environments or PR workflows
- Building vector search with
pgvector on Neon
- Debugging connection pool exhaustion or cold start issues
- Migrating from a traditional Postgres host to Neon
When NOT to use
- Long-running background jobs (Neon scales to zero — use RDS or a persistent DB instead)
- Very high connection count workloads without pooling (use PgBouncer or Neon's built-in pooler)
- When you need Postgres extensions Neon doesn't support yet
Instructions
Connection setup
Neon provides two connection strings — always use the pooled one in serverless:
DATABASE_URL="postgresql://user:pass@ep-xxx.pooler.us-east-2.aws.neon.tech/neondb?sslmode=require"
DATABASE_URL_DIRECT="postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require"
Drizzle ORM (recommended with Neon)
import { drizzle } from 'drizzle-orm/neon-http'
import { neon } from '@neondatabase/serverless'
import * as schema from './schema'
const sql = neon(process.env.DATABASE_URL!)
export const db = drizzle(sql, { schema })
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
const user = await db.query.users.findFirst({
where: (users, { eq }) => eq(users.email, 'alice@example.com'),
})
Prisma with Neon
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_URL_DIRECT")
}
npx prisma migrate deploy
Raw @neondatabase/serverless (Edge Runtime / Cloudflare)
import { neon } from '@neondatabase/serverless'
const sql = neon(process.env.DATABASE_URL!)
const users = await sql`SELECT * FROM users WHERE email = ${email}`
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const id = searchParams.get('id')
const [user] = await sql`SELECT id, email, name FROM users WHERE id = ${id}`
return Response.json(user)
}
export const runtime = 'edge'
Database branching — the killer feature
Neon lets you branch the database like git. Each branch is an instant copy-on-write snapshot — no data duplication until you write.
npm install -g neonctl
neonctl auth
neonctl branches create --name feature/add-payments --parent main
neonctl connection-string feature/add-payments
DATABASE_URL=$(neonctl connection-string feature/add-payments) npx prisma migrate dev
neonctl branches delete feature/add-payments
Automate with GitHub Actions:
name: Preview Environment
on: [pull_request]
jobs:
create-preview-db:
runs-on: ubuntu-latest
steps:
- uses: neondatabase/create-branch-action@v5
id: create-branch
with:
project_id: ${{ vars.NEON_PROJECT_ID }}
branch_name: preview/pr-${{ github.event.number }}
api_key: ${{ secrets.NEON_API_KEY }}
- name: Run migrations on preview branch
env:
DATABASE_URL: ${{ steps.create-branch.outputs.db_url_with_pooler }}
run: npx prisma migrate deploy
- name: Deploy to Vercel with preview DB
env:
pgvector for AI apps
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
metadata JSONB,
embedding vector(1536)
);
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
import { neon } from '@neondatabase/serverless'
import OpenAI from 'openai'
const sql = neon(process.env.DATABASE_URL!)
const openai = new OpenAI()
async function indexDocument(content: string, metadata: object) {
const { data } = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: content,
})
const embedding = data[0].embedding
await sql`
INSERT INTO documents (content, metadata, embedding)
VALUES (${content}, ${JSON.stringify(metadata)}, ${JSON.stringify(embedding)}::vector)
`
}
async function search(query: string, limit = 5) {
const { data } = await openai.embeddings.create({
: ,
: query,
})
embedding = data[].
sql
}
Autoscale to zero — connection handling
Neon computes scale to zero after inactivity. The first query after sleep takes ~500ms extra. Handle this in your app:
async function queryWithRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
for (let i = 0; i < retries; i++) {
try {
return await fn()
} catch (err) {
if (i === retries - 1) throw err
await new Promise(r => setTimeout(r, 200 * (i + 1)))
}
}
throw new Error('unreachable')
}
Neon + Vercel integration
neonctl integrations create vercel \
--project-id your-neon-project \
--vercel-project-id your-vercel-project
This automatically sets DATABASE_URL in Vercel per environment (production → main branch, preview → PR branches).
Example
User: Set up Neon with Drizzle ORM in a Next.js App Router project, with pgvector for semantic search and a GitHub Action that creates a DB branch for each PR.
Expected output:
db/index.ts — Neon serverless driver + Drizzle, pooled connection
db/schema.ts — users table + documents table with vector(1536) column
lib/search.ts — indexDocument() and search() using OpenAI embeddings
drizzle.config.ts — uses DATABASE_URL_DIRECT for migrations
.github/workflows/preview.yml — create-branch-action + migrate + Vercel deploy