Skip to content
Skillv1.0.0

neon

Use when you have picked Neon (serverless Postgres) and need to connect correctly from a serverless or edge runtime, wire database branching into dev, preview and CI, or stop being burned by scale-to-

by ericrisco(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from ericrisco/rsc-harness (skills/neon/SKILL.md). Install upstream with npx skills add ericrisco/rsc-harness --skill neon. Copyright stays with the author.

Neon — serverless Postgres as a platform

Neon is Postgres with three platform features the engine doesn't have: a serverless driver that talks over HTTP/WebSocket (so you can query from edge runtimes), built-in connection pooling, and copy-on-write branching that makes a fork of production data appear instantly. This skill owns that platform layer. The Postgres engine underneath — schema, indexes, EXPLAIN, RLS, zero-downtime DDL — is identical to any Postgres 16 and belongs to ../postgresdb/SKILL.md. Don't re-derive engine craft here; connect correctly and branch correctly.

When to use / When NOT to use

When to use:

  • Code imports @neondatabase/serverless, or DATABASE_URL points at *.neon.tech / *.aws.neon.tech.
  • Connecting from Vercel (Edge/Node), Cloudflare Workers, AWS Lambda, or any serverless/edge function — choosing HTTP neon() vs WebSocket Pool.
  • Setting up branching: dev branch off production, a branch per PR in CI, neonctl usage, the Neon + Vercel preview integration.
  • Neon-specific symptoms: cold start after scale-to-zero, "too many connections" despite being serverless, pooled-vs-direct string confusion, autoscaling CU sizing/cost.
  • Picking the connection string for migrations (direct) vs app queries (pooled).

When NOT to use — route to the sibling:

  • Schema design, index choice, EXPLAIN/ANALYZE, query tuning, RLS, VACUUM, partitioning, expand-contract migrations → ../postgresdb/SKILL.md. Neon adds nothing to the engine; defer.
  • Drizzle schema/migrations → ../drizzle-orm/SKILL.md; Prisma → prisma-orm. Neon supplies only the driver adapter line.
  • A different managed Postgres/BaaS: Supabase (auth + storage + realtime) → supabase; PlanetScale → planetscale; Turso/libSQL → sqlite-turso.
  • Where to deploy the app talking to Neon → ../vercel/SKILL.md / cloudflare / railway.
  • Backup/PITR strategy as a discipline → backups (Neon branch-as-restore is mentioned here, not owned).

Non-negotiables

  1. A WebSocket Pool/Client is created and closed inside one request handler — never at module scope in a serverless function. Module scope = a connection that outlives the invocation and leaks.
  2. App queries use the pooled string (host contains -pooler). Migrations / DDL / advisory locks / session features use the direct string (no -pooler). Mixing them is the #1 Neon foot-gun.
  3. The HTTP neon() driver always uses the pooled endpoint — don't hand it a direct string and expect session state.
  4. HTTP neon() has no interactive transactions. Need BEGIN ... COMMIT across round-trips? Use sql.transaction([...]) (batched, non-interactive) or a WebSocket Pool.
  5. On Node.js ≤ 21 you must set neonConfig.webSocketConstructor = ws before opening a Pool/Client. Node 22+ and edge runtimes have a global WebSocket; don't set it there.
  6. Pin the driver: @neondatabase/serverless is at v1.1.0 (npm latest, 2026-04-17). It is a drop-in replacement for pg.

Pick your connection method

Keyed by runtime and what the query needs. Pick the lightest transport that satisfies the need.

You need Use Why
One independent read/write per request (edge, Lambda, RSC) HTTP neon() Single fetch round-trip (~3 round trips vs ~8 for TCP setup); no connection to manage or leak.
Several queries that must be atomic, no logic between them sql.transaction([q1, q2]) over HTTP One round-trip, real transaction, still no socket lifecycle.
Interactive transaction — read a row, branch in app code, then write WebSocket Pool (open+close in handler) HTTP can't hold BEGIN open; WebSocket keeps the session alive within the request.
Full pg-API compatibility (cursors, LISTEN/NOTIFY, COPY) WebSocket Client/Pool HTTP is one-shot; node-postgres semantics need the socket.
Migrations / DDL / pg_advisory_lock / session GUCs direct (non--pooler) string with pg or WebSocket Client PgBouncer transaction-pooling breaks session-level state.

If you're behind Drizzle or Prisma, you don't call these directly — you pass the driver to the adapter. See the ORM pointers below.

Connection strings: pooled vs direct

Neon gives every branch two connection strings that differ in one thing: the host of the pooled one contains -pooler.

pooled:  postgresql://user:pass@ep-cool-name-123456-pooler.us-east-2.aws.neon.tech/db?sslmode=require
direct:  postgresql://user:pass@ep-cool-name-123456.us-east-2.aws.neon.tech/db?sslmode=require
Use case String Reason
App runtime (queries from your functions) pooled (-pooler) PgBouncer fans many short-lived serverless invocations onto a small pool; up to 10,000 concurrent client connections per project.
HTTP neon() driver pooled (it forces it regardless) The HTTP path is stateless; pooling is the right model.
Migrations, DDL, schema introspection direct Transaction pooling drops session state; advisory locks and prepared statements need a stable session.
psql interactive / debugging direct You want one real session, not a pooled handle.

Bad → Good for a typical setup:

# Bad: one string everywhere — migrations sporadically fail under PgBouncer.
DATABASE_URL="postgresql://...-pooler.../db?sslmode=require"

# Good: split them. App uses the pooled one; the migration tool uses DIRECT.
DATABASE_URL="postgresql://...-pooler.../db?sslmode=require"          # app runtime
DIRECT_URL="postgresql://....../db?sslmode=require"                    # migrations/DDL

Serverless driver patterns

All examples assume @neondatabase/serverless@1.1.0.

HTTP one-shot — the default for edge/serverless reads and writes:

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

const sql = neon(process.env.DATABASE_URL!); // pooled endpoint, no socket to manage
export async function getUser(id: string) {
  const [row] = await sql`select id, email from users where id = ${id}`;
  return row; // parameterized; ${id} is bound, not interpolated
}

Batched atomic writes over HTTP — atomicity without a WebSocket and without interactivity:

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

const sql = neon(process.env.DATABASE_URL!);
await sql.transaction([
  sql`insert into orders (id, total) values (${id}, ${total})`,
  sql`update inventory set qty = qty - 1 where sku = ${sku}`,
]); // one round-trip, both succeed or both roll back

Interactive transaction — WebSocket Pool, opened and closed inside the handler:

import { Pool, neonConfig } from "@neondatabase/serverless";
import ws from "ws"; // Node ≤21 only; omit on edge / Node 22+

neonConfig.webSocketConstructor = ws; // required on Node ≤21; do NOT set on edge runtimes

export async function transfer(from: string, to: string, cents: number) {
  const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // inside the handler
  const client = await pool.connect();
  try {
    await client.query("begin");
    const { rows } = await client.query("select balance from accounts where id=$1 for update", [from]);
    if (rows[0].balance < cents) throw new Error("insufficient");
    await client.query("update accounts set balance=balance-$1 where id=$2", [cents, from]);
    await client.query("update accounts set balance=balance+$1 where id=$2", [cents, to]);
    await client.query("commit");
  } catch (e) {
    await client.query("rollback");
    throw e;
  } finally {
    client.release();
    await pool.end(); // close before the function returns — no leaked connection
  }
}

Edge runtime (Vercel Edge, Cloudflare Workers): use HTTP neon(); the global WebSocket exists, so a Pool works without ws, but prefer HTTP unless you truly need an interactive transaction.

ORM adapters — you wire the driver, the ORM owns the query API:

  • Drizzle: pass neon()/Pool into drizzle-orm/neon-http or neon-serverless../drizzle-orm/SKILL.md.
  • Prisma: use the @prisma/adapter-neon driver adapter → prisma-orm.

Branching for dev / preview / CI

A Neon branch is copy-on-write: created instantly with no data copied, it forks production at a point in time, gets its own compute endpoint, and scales to zero independently. That makes a branch a cheap, isolated, full-data environment — not a backup substitute (test the restore).

Situation Do Why
Per-developer or per-PR isolated data branch off production Instant, real prod-shaped data, separate endpoint, auto scale-to-zero → near-free idle.
A genuinely separate product/tenant with its own billing & limits new project Branches share the project's quotas and pooled-connection ceiling.
Throwaway query you can run against staging same branch Don't manufacture a branch for a one-off SELECT.

neonctl (alias neon) drives create/connect/teardown:

neonctl branches create --name pr-$PR_NUMBER --parent main
neonctl connection-string pr-$PR_NUMBER --pooled   # feed to the preview app
neonctl branches delete pr-$PR_NUMBER              # on PR merge/close

Vercel preview integration: with the Neon + Vercel integration, each preview deployment gets its own branch forked from production, auto-deleted when the PR merges or closes — zero CI code. If you're not on Vercel, replicate it in CI: create branch on PR open → run migrations (direct string) → seed → expose the pooled connection string to the app → delete on PR close. Full GitHub Actions workflow, neonctl reference, and the Neon API endpoints: branching-ci.

Branch-as-restore (PITR): to recover, branch from an earlier point in time and either read from it or promote it. It's a fast undo, not a tested backup discipline — own that in backups.

Autoscaling & scale-to-zero

Each branch's compute endpoint autoscales between a min and max CU (vCPU/RAM) on its own, with no noisy neighbors across branches. Idle compute scales to zero after 5 minutes of inactivity (default), then resumes on the next connection with a millisecond-to-sub-second cold start.

Cost mental model: non-prod branches sit at zero almost always, so a dozen PR branches cost close to nothing. The cold start is the price.

Prod endpoint sizing checklist:

  • Set a sane min/max CU for the prod branch — not maxed "just in case" (you pay for the max headroom you actually use).
  • Latency-sensitive prod with sparse traffic? Disable scale-to-zero or set min CU > 0 so there's no cold start in p99.
  • Treat the cold start as real: warm the connection on deploy, or accept it on non-prod and account for it in latency SLOs.
  • Keep non-prod branches on scale-to-zero — that's where the near-free economics come from.

Anti-patterns

Anti-pattern Why it bites Do instead
new Pool(...) at module scope in a serverless function Connection outlives the invocation; you exhaust the pool → "too many connections". Create and await pool.end() inside the handler, or use HTTP neon().
Pooled (-pooler) string for migrations/DDL PgBouncer transaction-pooling drops session state; advisory locks/prepared statements fail intermittently. Use the direct string for migrations; pooled only for app runtime.
Direct (non--pooler) string as the app DATABASE_URL No pooling → serverless concurrency exhausts raw connections fast. App runtime always uses the pooled string.
Expecting BEGIN/interactive txns from HTTP neon() HTTP is one-shot, stateless — there's no open session to hold a transaction. sql.transaction([...]) for batched atomicity, or a WebSocket Pool for interactive.
Opening a Pool without neonConfig.webSocketConstructor = ws on Node ≤21 No global WebSocket → the connection silently fails. Set webSocketConstructor = ws on Node ≤21; omit on edge / Node 22+.
Ignoring scale-to-zero in p99 latency First request after 5 min idle pays the resume cold start; users see a slow request. Disable scale-to-zero or set min CU > 0 on latency-sensitive prod.
One shared branch for all PRs PRs clobber each other's data; no isolation. One branch per PR, deleted on close.
Treating a branch as a backup without testing restore Branches share project quotas and aren't an exercised recovery path. Verify branch-as-restore; own real backup strategy in backups.
Maxing CU "to be safe" You pay for headroom you don't use; autoscaling already handles spikes. Set a realistic min/max; let autoscaling expand.
Doing schema/index design in this skill Engine craft is identical to any Postgres; duplicating it drifts. Defer to ../postgresdb/SKILL.md.

Cross-references

  • Engine craft (schema, indexes, EXPLAIN, RLS, zero-downtime DDL): ../postgresdb/SKILL.md.
  • Next.js App Router data layer that calls Neon: ../nextjs/SKILL.md.
  • ORM query APIs over the Neon driver: ../drizzle-orm/SKILL.md, prisma-orm.
  • Deployment targets: ../vercel/SKILL.md, cloudflare, railway. Backups discipline: backups.
  • Other managed DBs (route away): supabase, planetscale, sqlite-turso.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/ericrisco-rsc-harness-neon/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

ericrisco-rsc-harness-neon.ocm.jsonjson
{
  "ocm": "1",
  "id": "ericrisco-rsc-harness-neon",
  "kind": "skill",
  "name": "neon",
  "description": "Use when you have picked Neon (serverless Postgres) and need to connect correctly from a serverless or edge runtime, wire database branching into dev, preview and CI, or stop being burned by scale-to-zero cold starts and pooler limits — including choosing HTTP versus WebSocket connections and pooled versus direct endpoints for migrations. NOT generic Postgres schema, index, EXPLAIN, RLS or migration-engine work (that is `postgresdb`).",
  "publisher": "ericrisco",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "general"
    ],
    "tags": [
      "skill-md",
      "neon",
      "postgres",
      "serverless",
      "database",
      "branching",
      "edge",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Use when you have picked Neon (serverless Postgres) and need to connect correctly from a serverless or edge runtime, wire database branching into dev, preview and CI, or stop being burned by scale-to-zero cold starts and pooler limits — including choosing HTTP versus WebSocket connections and pooled versus direct endpoints for migrations. NOT generic Postgres schema, index, EXPLAIN, RLS or migration-engine work (that is `postgresdb`)."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/ericrisco/rsc-harness",
      "path": "skills/neon/SKILL.md",
      "ref": "8cc4716ea549275ade1590ad270da01bdf837ab5",
      "url": "https://github.com/ericrisco/rsc-harness/blob/8cc4716ea549275ade1590ad270da01bdf837ab5/skills/neon/SKILL.md",
      "key": "ericrisco/rsc-harness/skills/neon/SKILL.md"
    }
  },
  "instructions": "# Neon — serverless Postgres as a platform\n\nNeon is Postgres with three platform features the engine doesn't have: a **serverless driver** that\ntalks over HTTP/WebSocket (so you can query from edge runtimes), **built-in connection pooling**, and\n**copy-on-write branching** that makes a fork of production data appear instantly. This skill owns\nthat platform layer. The Postgres *engine* underneath — schema, indexes, EXPLAIN, RLS, zero-downtime\nDDL — is identical to any Postgres 16 and belongs to `../postgresdb/SKILL.md`. Don't re-derive engine\ncraft here; connect correctly and branch correctly.\n",
  "cost": {
    "context_tokens": 3321
  }
}

Fetch it by URL: GET /api/v1/registry/ericrisco-rsc-harness-neon/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.