Neon Postgres Integration
This guide walks through connecting an AI agent to a Neon serverless Postgres database via Keystore. Your agent uses a ks_ token instead of a real database connection string. The Keystore proxy injects the real credentials at request time.
Prerequisites
- A Keystore account with a Neon provider configured
- An agent token (starts with
ks_) - A Neon database with a connection string
- Node.js 18+
Set things up with the CLI:
keystore login
keystore providers add # choose neon, byok, paste your connection string
keystore agents create # save the ks_ tokenInstall Dependencies
npm install @neondatabase/serverless @keystore/sdkOption A: Wrap the Neon Driver
The wrapNeon function creates a tagged template wrapper that routes SQL queries through the Keystore proxy. The proxy injects the real Neon connection string.
import { neon } from "@neondatabase/serverless";
import { Keystore, wrapNeon } from "@keystore/sdk";
const ks = new Keystore({ agentToken: "ks_abc123..." });
const sql = wrapNeon(neon, ks);
// Use the sql tagged template as usual.
const users = await sql`SELECT * FROM users LIMIT 10`;
console.log(users);The wrapped sql function has the same tagged template interface as the standard Neon driver. Write queries exactly as you normally would.
Option B: Intercept All Fetch Requests
interceptAll patches globalThis.fetch to reroute requests to Neon's serverless driver endpoint through the Keystore proxy.
import { neon } from "@neondatabase/serverless";
import { Keystore } from "@keystore/sdk";
const ks = new Keystore({ agentToken: "ks_abc123..." });
ks.interceptAll(["neon"]);
// The connection string is unused -- the proxy resolves it.
const sql = neon("postgresql://unused:unused@unused/unused");
const posts = await sql`SELECT * FROM posts WHERE published = true`;
console.log(posts);
ks.restore();Option C: Set Environment Variables
Use setupEnv to write DATABASE_URL into process.env. Useful when your ORM or framework reads the connection string from the environment.
import { Keystore } from "@keystore/sdk";
const ks = new Keystore({ agentToken: "ks_abc123..." });
ks.setupEnv(["neon"]);
// process.env.DATABASE_URL is now set to the proxy URL.
// Any framework that reads DATABASE_URL will route through Keystore.Using with Drizzle ORM
Keystore works with Drizzle ORM's Neon adapter. Wrap the underlying Neon driver, then pass it to Drizzle.
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import { Keystore, wrapNeon } from "@keystore/sdk";
import * as schema from "./schema";
const ks = new Keystore({ agentToken: "ks_abc123..." });
const sql = wrapNeon(neon, ks);
const db = drizzle(sql, { schema });
const users = await db.select().from(schema.users).limit(10);Alternatively, use setupEnv and let Drizzle read DATABASE_URL from the environment:
import { Keystore } from "@keystore/sdk";
const ks = new Keystore({ agentToken: "ks_abc123..." });
ks.setupEnv(["neon"]);
// In your Drizzle config or db initialization:
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
const sql = neon(process.env.DATABASE_URL!);
const db = drizzle(sql);Parameterized Queries
The wrapped driver supports parameterized queries through tagged templates, which protects against SQL injection.
const userId = 42;
const user = await sql`SELECT * FROM users WHERE id = ${userId}`;
const name = "Alice";
const email = "alice@example.com";
await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;Monitoring
Track database queries through the Keystore CLI:
keystore usage ag_abc123
keystore logs ag_abc123 --provider neonEach proxied query is logged with status, latency, and provider information.