Skip to content

ADR 0001: Production database provider

Status

Accepted.

Context

The platform deploys apps/cms and apps/lms as separate Vercel projects from this monorepo. Each app owns its own schema and database; they must not share a database because both schemas define tables such as users, tenants, and verification_tokens with different shapes.

The application data layer is intentionally plain Postgres:

  • Runtime access uses Kysely with the standard pg driver (apps/lms/src/db/dialect.ts).
  • Local development can use SQLite, but Vercel production requires managed PostgreSQL.
  • Auth is owned by Auth.js, not by the database provider.
  • Authorization is enforced in application code with tenant and user scoping, not with Postgres Row-Level Security.
  • Rate limiting uses Upstash Redis, and CMS asset storage uses Vercel Blob.

The current Vercel deployment strategy already models environments as database branches and uses the Vercel-Neon integration to inject pooled runtime and unpooled migration connection strings.

Decision

Use Neon as the production Postgres provider for both the CMS and LMS.

Supabase remains technically compatible because it provides standard Postgres connection strings, but its main differentiators are not needed by this architecture. Neon is a better fit for a Vercel-hosted monorepo that needs isolated production and preview databases while keeping auth, storage, cache, and authorization in separate application-owned layers.

Options considered

Neon

Pros:

  • Native Vercel integration supports automatic preview branches for each Preview deployment.
  • Copy-on-write branching gives isolated PR databases without manually provisioning a full database project per preview.
  • Pooled DATABASE_URL for runtime and unpooled DATABASE_URL_UNPOOLED for migrations match the repository's deploy scripts.
  • Serverless Postgres and scale-to-zero economics fit low-baseline workloads with bursty traffic and many short-lived previews.
  • The app continues to use plain Postgres through Kysely and pg, without adopting provider-specific APIs.
  • Keeps the existing separation of concerns: Auth.js for auth, app-level authorization, Upstash for rate limiting, and Vercel Blob for assets.

Cons:

  • Scale-to-zero can introduce first-request latency after idle periods.
  • Protected branches and higher branch limits depend on the selected Neon plan.
  • The platform must continue owning auth, storage, authorization, and realtime needs outside the database provider.

Supabase

Pros:

  • Provides a broad backend-as-a-service surface: Postgres, Auth, Storage, Realtime, and dashboard tooling.
  • Strong Row-Level Security tooling and examples.
  • Good fit if the platform later wants to consolidate auth, storage, and realtime into one provider.

Cons:

  • Supabase Auth would duplicate the existing Auth.js model.
  • Supabase Storage would duplicate the existing Vercel Blob plan.
  • Supabase Realtime is not currently required.
  • Supabase's RLS strengths are unused because authorization is intentionally enforced in application code.
  • Preview database branching is less directly aligned with Vercel's per-preview deployment model than the native Vercel-Neon integration.
  • Switching providers would require rewriting deployment topology and environment wiring without adding needed capabilities.

Consequences

The production topology remains:

  • One Neon project for the CMS.
  • One Neon project for the LMS.
  • A protected production branch in each project.
  • Automatic ephemeral preview branches for Vercel Preview deployments.
  • Runtime traffic over pooled Postgres endpoints.
  • Migrations and DDL over unpooled Postgres endpoints.
  • Build-time migration and system seeding through scripts/vercel-db.mjs (canonical script details: Root Scripts Reference).

The main operational risk is cold-start latency on branches that scale to zero. Mitigations include using Vercel Fluid Compute, routing runtime traffic through the pooled endpoint, and adding a lightweight production keep-warm check only if measured first-request latency becomes a user-facing issue.

Revisit criteria

Reconsider Supabase, or another provider, if one of these product or architecture requirements becomes important:

  • Postgres Row-Level Security becomes the primary authorization model.
  • Realtime subscriptions become a core product requirement.
  • The platform intentionally consolidates authentication, object storage, and database services into one backend-as-a-service provider.
  • Operational data scale, compliance requirements, or regional residency needs exceed the selected Neon plan's capabilities.