neon-serverless-postgres

Neon Serverless Postgres Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "neon-serverless-postgres" with this command: npx skills add bobmatnyc/claude-mpm-skills/bobmatnyc-claude-mpm-skills-neon-serverless-postgres

Neon Serverless Postgres Skill

progressive_disclosure: entry_point: summary: "Serverless Postgres with autoscaling, branching, and instant database provisioning" when_to_use:

  • "When needing serverless Postgres"
  • "When building edge and serverless apps"
  • "When implementing database branching for dev/staging"
  • "When using Drizzle, Prisma, or raw SQL" quick_start:
  • "Create project on Neon console"
  • "Get connection string"
  • "Connect with Drizzle/Prisma/pg"
  • "Deploy with Vercel/Netlify" token_estimate: entry: 75-90 full: 3800-4800

Core Concepts

Neon Architecture

  • Projects: Top-level container for databases and branches

  • Databases: Postgres databases within a project

  • Branches: Git-like database copies for development

  • Compute: Autoscaling Postgres instances

  • Storage: Separated from compute for instant branching

Key Features

  • Serverless: Pay-per-use, scales to zero

  • Branching: Instant database copies from any point in time

  • Autoscaling: Compute scales based on load

  • Instant Provisioning: Databases ready in seconds

  • Connection Pooling: Built-in PgBouncer support

Connection Strings

Standard Connection

Direct connection (for migrations, admin tasks)

DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname"

Pooled connection (for application queries)

DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname?sslmode=require"

Connection Pooling

PgBouncer pooled connection (recommended for serverless)

DATABASE_URL="postgresql://user:password@ep-xxx-pooler.region.aws.neon.tech/dbname?sslmode=require"

Direct connection for migrations

DIRECT_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname"

Drizzle ORM Integration

Setup

// drizzle.config.ts import type { Config } from "drizzle-kit";

export default { schema: "./src/db/schema.ts", out: "./drizzle", driver: "pg", dbCredentials: { connectionString: process.env.DATABASE_URL!, }, } satisfies Config;

// src/db/index.ts import { drizzle } from "drizzle-orm/neon-http"; import { neon } from "@neondatabase/serverless";

const sql = neon(process.env.DATABASE_URL!); export const db = drizzle(sql);

Schema Definition

// src/db/schema.ts import { pgTable, serial, text, timestamp, varchar } from "drizzle-orm/pg-core";

export const users = pgTable("users", { id: serial("id").primaryKey(), name: varchar("name", { length: 255 }).notNull(), email: varchar("email", { length: 255 }).notNull().unique(), createdAt: timestamp("created_at").defaultNow(), });

export const posts = pgTable("posts", { id: serial("id").primaryKey(), title: text("title").notNull(), content: text("content"), userId: serial("user_id").references(() => users.id), createdAt: timestamp("created_at").defaultNow(), });

Queries

import { db } from "./db"; import { users, posts } from "./db/schema"; import { eq } from "drizzle-orm";

// Insert const newUser = await db.insert(users).values({ name: "John Doe", email: "john@example.com", }).returning();

// Query const allUsers = await db.select().from(users);

// Join const userPosts = await db .select() .from(posts) .leftJoin(users, eq(posts.userId, users.id));

// Update await db.update(users) .set({ name: "Jane Doe" }) .where(eq(users.id, 1));

Migrations

Generate migration

npx drizzle-kit generate:pg

Run migration (use direct connection)

npx drizzle-kit push:pg

Or use custom script

src/db/migrate.ts

import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres";

const sql = postgres(process.env.DIRECT_URL!, { max: 1 }); const db = drizzle(sql);

await migrate(db, { migrationsFolder: "./drizzle" }); await sql.end();

Prisma Integration

Setup

// prisma/schema.prisma generator client { provider = "prisma-client-js" }

datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DIRECT_URL") // For migrations }

model User { id Int @id @default(autoincrement()) name String email String @unique posts Post[] createdAt DateTime @default(now()) }

model Post { id Int @id @default(autoincrement()) title String content String? userId Int user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) }

Client Usage

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

// Create const user = await prisma.user.create({ data: { name: "John Doe", email: "john@example.com", }, });

// Query with relations const userWithPosts = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: true }, });

// Transaction await prisma.$transaction([ prisma.user.create({ data: { name: "User 1", email: "u1@example.com" } }), prisma.user.create({ data: { name: "User 2", email: "u2@example.com" } }), ]);

Migrations

Create migration

npx prisma migrate dev --name init

Deploy to production (uses DIRECT_URL)

npx prisma migrate deploy

Generate client

npx prisma generate

Node-Postgres (pg) Integration

Direct Connection

import { Pool } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: { rejectUnauthorized: false }, });

// Query const result = await pool.query("SELECT * FROM users WHERE email = $1", [ "john@example.com", ]);

// Transaction const client = await pool.connect(); try { await client.query("BEGIN"); await client.query("INSERT INTO users (name, email) VALUES ($1, $2)", [ "John", "john@example.com", ]); await client.query("COMMIT"); } catch (e) { await client.query("ROLLBACK"); throw e; } finally { client.release(); }

Serverless Driver

import { neon, neonConfig } from "@neondatabase/serverless";

// Configure for edge runtime neonConfig.fetchConnectionCache = true;

const sql = neon(process.env.DATABASE_URL!);

// Execute query const result = await sqlSELECT * FROM users WHERE email = ${email};

// Transactions const [user] = await sql.transaction([ sqlINSERT INTO users (name, email) VALUES (${name}, ${email}) RETURNING *, sqlINSERT INTO audit_log (action) VALUES ('user_created'), ]);

Database Branching

Branch Types

  • Main: Production branch

  • Development: Feature development

  • Preview: PR/deployment previews

  • Testing: QA and testing environments

Creating Branches

Via CLI

neonctl branches create --name dev --parent main

Via API

curl -X POST https://console.neon.tech/api/v2/projects/{project_id}/branches
-H "Authorization: Bearer $NEON_API_KEY"
-d '{"name": "dev", "parent_id": "main"}'

Via Console

Navigate to project → Branches → Create branch

Branch Workflows

Feature Development

1. Create feature branch

neonctl branches create --name feature/user-auth --parent dev

2. Get connection string

neonctl connection-string feature/user-auth

3. Update .env.local

DATABASE_URL="postgresql://...feature-user-auth..."

4. Run migrations

npm run migrate

5. Develop and test

6. Merge changes (via schema migration)

7. Delete branch

neonctl branches delete feature/user-auth

Preview Deployments

// vercel.json { "env": { "DATABASE_URL": "@database-url-main" }, "build": { "env": { "DATABASE_URL": "@database-url-preview" } } }

// Create preview branch on deploy // .github/workflows/preview.yml

  • name: Create Neon Branch run: | BRANCH_NAME="preview-${{ github.event.number }}" neonctl branches create --name $BRANCH_NAME --parent main DATABASE_URL=$(neonctl connection-string $BRANCH_NAME) echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV

Point-in-Time Recovery

Create branch from specific timestamp

neonctl branches create --name recovery
--parent main
--timestamp "2024-01-15T10:30:00Z"

Restore from branch

neonctl branches reset main --from recovery

Vercel Integration

Automatic Setup

Install Vercel CLI

npm i -g vercel

Link project

vercel link

Add Neon integration

vercel integration add neon

Vercel automatically:

- Creates main branch connection

- Creates preview branch per PR

- Sets DATABASE_URL environment variable

Manual Configuration

Add to Vercel project settings

vercel env add DATABASE_URL

For preview branches

vercel env add DATABASE_URL preview

For production

vercel env add DATABASE_URL production

Next.js Integration

// app/api/users/route.ts import { neon } from "@neondatabase/serverless";

export const runtime = "edge";

export async function GET() { const sql = neon(process.env.DATABASE_URL!); const users = await sqlSELECT * FROM users;

return Response.json(users); }

// app/api/users/[id]/route.ts export async function GET( request: Request, { params }: { params: { id: string } } ) { const sql = neon(process.env.DATABASE_URL!); const [user] = await sqlSELECT * FROM users WHERE id = ${params.id};

if (!user) { return new Response("Not found", { status: 404 }); }

return Response.json(user); }

Connection Pooling

PgBouncer Pooling

// Use pooled connection for queries const pooledDb = drizzle(neon(process.env.DATABASE_URL!));

// Use direct connection for migrations const directDb = drizzle(neon(process.env.DIRECT_URL!));

// package.json scripts { "scripts": { "migrate": "DATABASE_URL=$DIRECT_URL drizzle-kit push:pg", "dev": "next dev" } }

Connection Limits

// Configure pool size import { Pool } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, // Max connections idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, });

// For serverless, use Neon's serverless driver import { neon } from "@neondatabase/serverless"; // Automatically handles connection pooling

Autoscaling and Compute

Compute Units

Free Tier

  • 0.25 Compute Units (CU)
  • Scales to zero when idle
  • Shared compute

Pro Tier

  • 0.25 - 4 CU autoscaling
  • Configurable min/max
  • Dedicated compute

Configuration

Via CLI

neonctl set-compute --min 0.25 --max 2 --branch main

Via API

curl -X PATCH https://console.neon.tech/api/v2/projects/{id}/branches/{branch_id}
-d '{"compute": {"min_cu": 0.25, "max_cu": 2}}'

Autoscaling Strategy

// Development: Scale to zero // min: 0.25 CU, max: 1 CU

// Staging: Minimal baseline // min: 0.5 CU, max: 2 CU

// Production: Always-on baseline // min: 1 CU, max: 4 CU

// Configure per branch const computeConfig = { dev: { min: 0.25, max: 1 }, staging: { min: 0.5, max: 2 }, main: { min: 1, max: 4 }, };

Read Replicas

Setup

Create read replica

neonctl read-replica create --branch main --region us-east-1

Get connection string

neonctl connection-string --replica

Usage Pattern

// Write to primary const writeDb = drizzle(neon(process.env.DATABASE_URL!));

// Read from replica const readDb = drizzle(neon(process.env.DATABASE_URL_REPLICA!));

// Application logic async function getUser(id: number) { return await readDb.select().from(users).where(eq(users.id, id)); }

async function updateUser(id: number, data: any) { return await writeDb.update(users).set(data).where(eq(users.id, id)); }

// Load balancing const replicas = [ process.env.DATABASE_URL_REPLICA_1!, process.env.DATABASE_URL_REPLICA_2!, ];

function getReadConnection() { const url = replicas[Math.floor(Math.random() * replicas.length)]; return drizzle(neon(url)); }

CLI Usage

Installation

npm install -g neonctl

Or use npx

npx neonctl --help

Common Commands

Authentication

neonctl auth

List projects

neonctl projects list

Create project

neonctl projects create --name my-app

List branches

neonctl branches list

Create branch

neonctl branches create --name dev --parent main

Get connection string

neonctl connection-string main

Database operations

neonctl databases create --name analytics neonctl databases list

Compute settings

neonctl set-compute --min 0.5 --max 2

Delete branch

neonctl branches delete dev

Migration Strategies

Drizzle Migrations

// drizzle/migrate.ts import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres";

const runMigrations = async () => { const connection = postgres(process.env.DIRECT_URL!, { max: 1 }); const db = drizzle(connection);

console.log("Running migrations..."); await migrate(db, { migrationsFolder: "./drizzle" }); console.log("Migrations complete!");

await connection.end(); };

runMigrations();

Prisma Migrations

Development

npx prisma migrate dev --name add_users_table

Production (uses DIRECT_URL)

npx prisma migrate deploy

Reset database (dev only)

npx prisma migrate reset

Zero-Downtime Migrations

-- 1. Add new column (nullable) ALTER TABLE users ADD COLUMN new_email VARCHAR(255);

-- 2. Backfill data UPDATE users SET new_email = email;

-- 3. Make non-nullable (after verification) ALTER TABLE users ALTER COLUMN new_email SET NOT NULL;

-- 4. Drop old column ALTER TABLE users DROP COLUMN email;

-- 5. Rename column ALTER TABLE users RENAME COLUMN new_email TO email;

Branch-Based Migrations

1. Create migration branch

neonctl branches create --name migration/add-index --parent main

2. Test migration on branch

DATABASE_URL=$(neonctl connection-string migration/add-index)
npm run migrate

3. Verify on branch

DATABASE_URL=$(neonctl connection-string migration/add-index)
npm run test

4. Apply to main

npm run migrate:production

5. Delete migration branch

neonctl branches delete migration/add-index

Best Practices

Serverless Optimization

// ✅ Use Neon serverless driver for edge import { neon } from "@neondatabase/serverless"; const sql = neon(process.env.DATABASE_URL!);

// ✅ Enable connection caching import { neonConfig } from "@neondatabase/serverless"; neonConfig.fetchConnectionCache = true;

// ✅ Use pooled connections const pooledUrl = process.env.DATABASE_URL; // -pooler endpoint

// ❌ Don't use standard pg in edge runtime // import { Pool } from "pg"; // Won't work in edge

Connection Management

// ✅ Reuse connections in serverless let cachedDb: ReturnType<typeof drizzle> | null = null;

function getDb() { if (!cachedDb) { const sql = neon(process.env.DATABASE_URL!); cachedDb = drizzle(sql); } return cachedDb; }

// ✅ Use transactions for consistency await db.transaction(async (tx) => { await tx.insert(users).values({ name: "John" }); await tx.insert(auditLog).values({ action: "user_created" }); });

// ❌ Don't forget to close pools in long-running processes // await pool.end();

Branch Strategy

Environments: main: Production data staging: Pre-production testing dev: Shared development feature/: Individual features preview/: PR previews (auto-created)

Lifecycle:

  • Create from parent on feature start
  • Run migrations independently
  • Test thoroughly
  • Merge schema changes
  • Delete after feature completion

Cost Optimization

// Development: Scale to zero // - min_cu: 0.25 // - Suspend after 5 minutes idle

// Staging: Minimal always-on // - min_cu: 0.5 // - Reduce during off-hours

// Production: Right-size baseline // - min_cu: Based on traffic patterns // - max_cu: Handle peak load

// Branch cleanup // Delete unused preview branches after PR merge

Environment Variables

Required Variables

Neon connection strings

DATABASE_URL="postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/db?sslmode=require" DIRECT_URL="postgresql://user:pass@ep-xxx.region.aws.neon.tech/db?sslmode=require"

API access (for CLI/automation)

NEON_API_KEY="your_api_key"

Project configuration

NEON_PROJECT_ID="your_project_id"

Multi-Environment Setup

.env.local (development)

DATABASE_URL="postgresql://...dev-branch..."

.env.staging

DATABASE_URL="postgresql://...staging-branch..."

.env.production (via Vercel)

DATABASE_URL="postgresql://...main-branch..."

Common Patterns

API Route with Caching

import { neon } from "@neondatabase/serverless";

export const runtime = "edge";

export async function GET() { const sql = neon(process.env.DATABASE_URL!);

const users = await sqlSELECT * FROM users ORDER BY created_at DESC LIMIT 10;

return Response.json(users, { headers: { "Cache-Control": "s-maxage=60, stale-while-revalidate", }, }); }

Server Actions (Next.js)

"use server";

import { neon } from "@neondatabase/serverless"; import { revalidatePath } from "next/cache";

export async function createUser(formData: FormData) { const sql = neon(process.env.DATABASE_URL!);

const name = formData.get("name") as string; const email = formData.get("email") as string;

await sqlINSERT INTO users (name, email) VALUES (${name}, ${email});

revalidatePath("/users"); }

Connection Testing

async function testConnection() { const sql = neon(process.env.DATABASE_URL!);

try { const result = await sqlSELECT version(); console.log("✅ Connected to Neon:", result[0].version); return true; } catch (error) { console.error("❌ Connection failed:", error); return false; } }

Troubleshooting

Connection Issues

// Check SSL requirement const url = new URL(process.env.DATABASE_URL!); if (!url.searchParams.has("sslmode")) { url.searchParams.set("sslmode", "require"); }

// Verify endpoint type // -pooler: For application queries // direct: For migrations and admin tasks

// Test connectivity import { neon } from "@neondatabase/serverless"; const sql = neon(process.env.DATABASE_URL!); await sqlSELECT 1; // Should succeed

Migration Failures

Use direct connection for migrations

export DIRECT_URL="postgresql://...direct-endpoint..." npx prisma migrate deploy

Check migration status

npx prisma migrate status

Force reset (dev only)

npx prisma migrate reset

Performance Issues

// Enable query logging import { drizzle } from "drizzle-orm/neon-http"; const db = drizzle(sql, { logger: true });

// Check slow queries in Neon console // Monitoring → Query Performance

// Add indexes await sqlCREATE INDEX idx_users_email ON users(email);

// Use connection pooling // Ensure using -pooler endpoint

This skill provides comprehensive coverage of Neon serverless Postgres, including database branching, ORM integrations, serverless deployment patterns, and production best practices.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

drizzle-orm

No summary provided by upstream source.

Repository SourceNeeds Review
General

pydantic

No summary provided by upstream source.

Repository SourceNeeds Review
General

playwright-e2e-testing

No summary provided by upstream source.

Repository SourceNeeds Review