Neon
Use Neon serverless PostgreSQL with Quickback. Connection modes, RLS policy generation, Neon Authorize setup, Drizzle integration.
Neon provides serverless PostgreSQL that works with Cloudflare Workers via HTTP connections. Choose Neon when you need PostgreSQL features (JSON operators, full-text search, advanced indexing) or want database-level Row Level Security alongside the generated Hono API.
Why Neon
- Full PostgreSQL — Advanced queries, joins, PostGIS, full-text search
- Database-level security — RLS policies enforce access even if the API is bypassed
- Serverless — HTTP connection mode for Cloudflare Workers
- Neon Authorize — JWT-based RLS via the
auth.user_id()function - Better Auth integration — Works seamlessly with the Quickback auth provider
Configuration
import { defineConfig, defineRuntime, defineDatabase, defineAuth } from "@quickback/compiler";
export default defineConfig({
name: "my-app",
providers: {
runtime: defineRuntime("cloudflare"),
database: defineDatabase("neon", {
connectionMode: "auto", // detects runtime
pooled: true,
}),
auth: defineAuth("better-auth"),
},
});Connection modes
| Mode | Best for | How it works |
|---|---|---|
| HTTP | Cloudflare Workers, edge functions | Stateless HTTP queries via @neondatabase/serverless |
| WebSocket | Node.js, Bun | Persistent WebSocket connection for lower latency |
| Auto (default) | Mixed environments | Detects runtime and picks the best mode |
Environment variables
# For Cloudflare Workers
npx wrangler secret put DATABASE_URL
# Paste: postgresql://user:pwd@ep-xxx.region.neon.tech/db?sslmode=require
# For local development (.env)
DATABASE_URL=postgresql://user:pwd@ep-xxx.region.neon.tech/db?sslmode=requireMigrations
Neon uses PostgreSQL migrations, distinct from D1's SQLite migrations:
quickback compile # generates migrations under quickback/drizzle/
npm run db:migrate # applies them — schema AND the RLS layerThe RLS layer (helper functions, policies, triggers) rides the same
journaled migration set as your schema (v0.45+): the compiler appends a
content-addressed <idx>_quickback_rls_<hash> migration, so one
db:migrate applies everything in order. The RLS migration is idempotent
(DROP POLICY IF EXISTS + re-create) and only a changed security config
appends a new entry — recompiles with unchanged security append nothing. No
manual SQL application step exists anymore.
Row Level Security
Quickback emits RLS policies from your firewall and access config. The Neon target uses Neon Authorize's auth.user_id() JWT claim function (the Supabase target uses auth.uid() instead — same pattern, different function).
firewall: [
{ field: 'organizationId', equals: 'ctx.activeOrgId' },
]CREATE POLICY "documents_select" ON documents FOR SELECT
USING (
organization_id = get_active_org_id()
AND (has_any_role(ARRAY['admin']) OR user_id = auth.user_id())
);Firewall patterns
Organization-scoped:
CREATE POLICY "projects_select" ON projects FOR SELECT
USING (organization_id = public.get_active_org_id());User-scoped:
CREATE POLICY "preferences_select" ON preferences FOR SELECT
USING (user_id = auth.user_id());Public tables:
CREATE POLICY "categories_deny_anon" ON categories FOR ALL TO anon USING (false);
CREATE POLICY "categories_all" ON categories FOR ALL TO authenticated USING (true) WITH CHECK (true);Helper functions
| Function | Purpose |
|---|---|
get_active_org_id() | Returns the user's active organization from user_sessions |
has_any_role(roles[]) | Checks if the user has any specified role |
has_org_role(role) | Checks for a single specific role |
is_org_member() | Checks org membership |
is_owner(owner_id) | Checks record ownership |
All helpers are TEXT-typed to match Better Auth's prefixed ids
(org_abc123). The user_sessions table the org helpers read is
compiler-owned migration state (src/db/rls-support.schema.ts), created by
the journaled migrations and kept in sync automatically: generated Better
Auth session hooks mirror the session's active organization (and team) into
it on every session create/update. Switching to "no org" revokes SQL-side
access — fail closed.
RLS coverage includes Better Auth's own tables — including those the
compiler force-enables (organization, admin, apikeys; plus the OAuth/agent
table families when those surfaces are on, locked to service-role only).
Setting up Neon Authorize
- Open Project Settings → Authorize in the Neon console
- Add your JWKS URL:
https://your-api.example.com/auth/v1/.well-known/jwks.json - The
auth.user_id()function will return the authenticated user's ID from the JWT
Generated files
quickback/drizzle/
├── 0000_<name>.sql # schema migrations (drizzle-kit)
├── 0001_quickback_rls_<hash>.sql # journaled RLS layer (content-addressed)
└── meta/_journal.json # one journal covers both
src/
├── db/schema.ts # Drizzle schema barrel
├── db/rls-support.schema.ts # user_sessions + RLS support tables
└── auth/schema.ts # Better Auth schema (owned by the BA CLI)When to choose Neon vs D1
| Factor | Neon | D1 |
|---|---|---|
| SQL dialect | PostgreSQL | SQLite |
| Security model | RLS + application-layer | Application-layer only |
| Multi-region | Built-in replication | Single region per database |
| Pricing | Free tier, then usage-based | Free tier, then usage-based |
| Best for | Complex queries, existing Postgres apps | Edge-first, simple schemas |