Files
paperclip/doc/DATABASE.md
Forgotten cc24722090 Replace PGlite with embedded-postgres and add startup banner
Switch from PGlite (WebAssembly) to embedded-postgres for zero-config
local development — provides a real PostgreSQL server with full
compatibility. Add startup banner with config summary on server boot.
Improve server bootstrap with auto port detection, database creation,
and migration on startup. Update DATABASE.md, DEVELOPING.md, and
SPEC-implementation.md to reflect the change. Update CLI database
check and prompts. Simplify OnboardingWizard database options.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:45:43 -06:00

3.3 KiB

Database

Paperclip uses PostgreSQL via Drizzle ORM. There are three ways to run the database, from simplest to most production-ready.

1. Embedded PostgreSQL — zero config

If you don't set DATABASE_URL, the server automatically starts an embedded PostgreSQL instance and manages a local data directory.

pnpm dev

That's it. On first start the server:

  1. Creates a ./server/data/embedded-postgres/ directory for storage
  2. Ensures the paperclip database exists
  3. Runs migrations automatically for empty databases
  4. Starts serving requests

Data persists across restarts in ./server/data/embedded-postgres/. To reset local dev data, delete that directory.

This mode is ideal for local development and one-command installs.

2. Local PostgreSQL (Docker)

For a full PostgreSQL server locally, use the included Docker Compose setup:

docker compose up -d

This starts PostgreSQL 17 on localhost:5432. Then set the connection string:

cp .env.example .env
# .env already contains:
# DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip

Run migrations (once the migration generation issue is fixed) or use drizzle-kit push:

DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
  npx drizzle-kit push

Start the server:

pnpm dev

3. Hosted PostgreSQL (Supabase)

For production, use a hosted PostgreSQL provider. Supabase is a good option with a free tier.

Setup

  1. Create a project at database.new
  2. Go to Project Settings > Database > Connection string
  3. Copy the URI and replace the password placeholder with your database password

Connection string

Supabase offers two connection modes:

Direct connection (port 5432) — use for migrations and one-off scripts:

postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres

Connection pooling via Supavisor (port 6543) — use for the application:

postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres

Configure

Set DATABASE_URL in your .env:

DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres

If using connection pooling (port 6543), the postgres client must disable prepared statements. Update packages/db/src/client.ts:

export function createDb(url: string) {
  const sql = postgres(url, { prepare: false });
  return drizzlePg(sql, { schema });
}

Push the schema

# Use the direct connection (port 5432) for schema changes
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@...5432/postgres \
  npx drizzle-kit push

Free tier limits

  • 500 MB database storage
  • 200 concurrent connections
  • Projects pause after 1 week of inactivity

See Supabase pricing for current details.

Switching between modes

The database mode is controlled by DATABASE_URL:

DATABASE_URL Mode
Not set Embedded PostgreSQL (./server/data/embedded-postgres/)
postgres://...localhost... Local Docker PostgreSQL
postgres://...supabase.com... Hosted Supabase

Your Drizzle schema (packages/db/src/schema/) stays the same regardless of mode.