Neon serverless Postgres - branching, connection pooling, autoscaling, and best practices. Use when working with Neon database or serverless Postgres.
# Using Neon CLI
npm install -g neonctl
neonctl auth
# Create project
neonctl projects create --name my-project
# Get connection string
neonctl connection-string
postgresql://[user]:[password]@[endpoint].neon.tech/[database]?sslmode=require
// Use Neon serverless driver for edge/serverless
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
export async function getUsers() {
const users = await sql`SELECT * FROM users LIMIT 10`;
return users;
}
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);
// Usage
const users = await db.select().from(usersTable).limit(10);
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: true },
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
// Use pooled connections
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users');
return result.rows;
} finally {
client.release();
}
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL") // For migrations
}
// For edge/serverless
import { PrismaClient } from '@prisma/client'
import { PrismaNeon } from '@prisma/adapter-neon'
import { neon } from '@neondatabase/serverless'
const sql = neon(process.env.DATABASE_URL!)
const adapter = new PrismaNeon(sql)
const prisma = new PrismaClient({ adapter })
# Create branch from main
neonctl branches create --name dev --parent main
# Get branch connection string
neonctl connection-string --branch dev
# List branches
neonctl branches list
# Delete branch
neonctl branches delete dev
## Development Workflow
1. **Main Branch**: Production data
2. **Dev Branch**: Daily development
3. **Feature Branches**: Per-feature isolation
main (production) ├── dev (shared development) │ ├── feature/auth │ └── feature/payments └── staging (pre-production)
### CI/CD Branch Strategy
```yaml
# .github/workflows/preview.yml
name: Preview Environment
on:
pull_request:
types: [opened, synchronize]
jobs:
create-preview:
runs-on: ubuntu-latest
steps:
- name: Create Neon Branch
uses: neondatabase/create-branch-action@v4
with:
project_id: ${{ secrets.NEON_PROJECT_ID }}
branch_name: preview-${{ github.event.pull_request.number }}
api_key: ${{ secrets.NEON_API_KEY }}
- name: Run Migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ steps.create-preview.outputs.db_url }}
// Neon autoscales based on load
// Configure min/max compute units in dashboard
// For predictable workloads, set minimum compute
// Project Settings > Compute > Minimum compute size
// Scale to zero saves costs during inactivity
// Re-activation takes ~500ms (cold start)
// Warm connection on app start
async function warmDatabase() {
const sql = neon(process.env.DATABASE_URL!);
await sql`SELECT 1`;
}
// Call during initialization
warmDatabase().catch(console.error);
-- Use EXPLAIN ANALYZE
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
-- Add appropriate indexes
CREATE INDEX idx_users_email ON users(email);
-- Use partial indexes for common filters
CREATE INDEX idx_active_users ON users(created_at)
WHERE status = 'active';
// DON'T: Create new connection per request
async function handleRequest() {
const pool = new Pool({ connectionString: url }); // BAD
const result = await pool.query('SELECT 1');
await pool.end();
}
// DO: Reuse connection pool
const pool = new Pool({ connectionString: url }); // Create once
async function handleRequest() {
const result = await pool.query('SELECT 1');
return result;
}
// For serverless: Use HTTP-based driver
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// For long-running: Use WebSocket for better performance
import { neonConfig, Pool } from '@neondatabase/serverless';
import ws from 'ws';
neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: url });
# .env.local
DATABASE_URL="postgresql://user:pass@ep-xxx.region.aws.neon.tech/dbname?sslmode=require"
# For Prisma migrations (bypasses pooler)
DIRECT_URL="postgresql://user:pass@ep-xxx.region.aws.neon.tech/dbname?sslmode=require"
# Connect Neon to Vercel
# 1. Go to Neon Dashboard > Integrations
# 2. Select Vercel
# 3. Authorize and select project
# Environment variables are set automatically
## Available in Neon Console
- Connection count
- Query performance
- Storage usage
- Compute hours
- Data transfer
-- Enable query statistics
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find slow queries
SELECT
query,
calls,
mean_exec_time,
total_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
# Development
npx prisma migrate dev --name add_users
# Production
npx prisma migrate deploy
# Generate migration
npx drizzle-kit generate:pg
# Apply migration
npx drizzle-kit push:pg