Database Setup

diy-analytics stores everything in PostgreSQL via Drizzle ORM, accessed with the postgres-js driver (lib/db.ts). Any Postgres 14+ works — there's no provider-specific SDK, just a DATABASE_URL connection string.

Connecting a provider

Set DATABASE_URL in .env.local (or your hosting platform's environment variables), then apply the schema:

bash
npm run db:migrate

A quick note on connection pooling, since Neon and Supabase both ask you to choose between two connection strings: your app can only hold so many direct connections to the database open at once, and a serverless host like Vercel can spin up many short-lived instances of your app at the same time — each wanting its own connection. A "pooler" sits in front of the database and shares a smaller set of real connections across all of them, so you don't run out. That's why, below, you're told to copy the pooled string rather than the direct one — the direct one will work at low traffic and then start failing with connection errors as traffic grows.

Neon

Use the pooled connection string — the one with -pooler in the hostname, not the direct one:

.env.local
DATABASE_URL=postgres://user:password@ep-xxxx-pooler.region.aws.neon.tech/dbname?sslmode=require&channel_binding=require

You don't need to configure anything else for this to work — lib/db.ts already talks to pooled connections correctly out of the box.

Supabase

Grab the connection string from Settings → Database → Connection string, and use Transaction pooler mode (port 6543), not Session mode (port 5432) — same pooling reasoning as Neon above:

.env.local
DATABASE_URL=postgres://postgres.xxxx:password@aws-0-region.pooler.supabase.com:6543/postgres

That's the only setting you need. No other Supabase env vars (SUPABASE_URL, SUPABASE_ANON_KEY, etc.) are required — diy-analytics only speaks plain Postgres, it never touches Supabase's client library or its Auth/API layer.

Vercel Postgres

Use the connection string Vercel provisions for you (Project → Storage tab) as-is:

.env.local
DATABASE_URL=postgres://user:password@host.postgres.vercel-storage.com/dbname?sslmode=require

Railway, self-hosted, or other Postgres

Any standard connection string works:

.env.local
DATABASE_URL=postgres://user:password@host:5432/dbname?sslmode=require

If you're running your own Postgres behind a connection pooler in transaction mode, the same handling described above applies automatically — no code changes needed on your end.

Ingestion requires Vercel

Your database can be any provider listed above. But diy-analyticsthe app itself — needs to be deployed on Vercel for tracking to actually work. These are two separate things, and mixing them up is the most common way a new deployment ends up with an empty dashboard.

Here's why: when a visitor loads your website, the tracking script sends its data to /api/track. On Vercel, that request gets handed off to a background job (built on Vercel Queues) that safely writes it into your database, even under heavy traffic. That background job is a piece of Vercel's own infrastructure — it doesn't exist, and can't be recreated, on Docker, Railway, or a plain Node server.

Everything else — the dashboard itself, every other API route, and the daily rollup cron — has no Vercel-specific dependency and runs fine on any Node host.

Next steps