Quickback Docs
Quickback for Hono API

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

quickback/quickback.config.ts
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

ModeBest forHow it works
HTTPCloudflare Workers, edge functionsStateless HTTP queries via @neondatabase/serverless
WebSocketNode.js, BunPersistent WebSocket connection for lower latency
Auto (default)Mixed environmentsDetects 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=require

Migrations

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 layer

The 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

FunctionPurpose
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

  1. Open Project Settings → Authorize in the Neon console
  2. Add your JWKS URL: https://your-api.example.com/auth/v1/.well-known/jwks.json
  3. 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

FactorNeonD1
SQL dialectPostgreSQLSQLite
Security modelRLS + application-layerApplication-layer only
Multi-regionBuilt-in replicationSingle region per database
PricingFree tier, then usage-basedFree tier, then usage-based
Best forComplex queries, existing Postgres appsEdge-first, simple schemas

See also

  • D1 — SQLite at the edge
  • Providers — All database provider options

On this page