@cookiepal-oss/backend
v1.0.0
Published
Self-hostable consent capture server for the cookiepal banner.
Readme
@cookiepal-oss/backend
Self-hostable consent-capture server. Receives consent events from the @cookiepal-oss/consent browser SDK and stores them in Postgres. One instance per site.
Built on Hono + pg. Server-stamps IP, User-Agent, timestamp, and edge-provided geo, so clients cannot spoof these.
Quickstart (local dev)
Requires a Postgres 14+ instance. The backend auto-runs migrations on startup.
SITE=http://localhost:3000 \
DATABASE_URL=postgres://postgres:postgres@localhost:5432/cookiepal \
pnpm --filter @cookiepal-oss/backend dev
# listens on :8080Smoke test:
curl -X POST http://localhost:8080/consent \
-H 'Content-Type: application/json' \
-H 'Origin: http://localhost:3000' \
-d '{"consentId":"test-1","consent":{"necessary":true,"analytics":false}}'
# → {"ok":true} (HTTP 201)
curl http://localhost:8080/health
# → {"status":"ok"}Configuration
The backend is configured entirely via environment variables. No CLI flags.
| Env var | Required | Default | Purpose |
|---|---|---|---|
| SITE | yes | (none) | Site origin this backend serves (e.g. https://example.com). Stamped onto every row. |
| DATABASE_URL | yes | (none) | Postgres connection string (postgres://user:pass@host:5432/db) |
| ORIGINS | no | SITE | Comma-separated CORS allowlist. Exact-match, no wildcards. |
| PORT | no | 8080 | Listen port |
| LOG_LEVEL | no | info | debug / info / warn / error |
Deploy with Docker
docker build -f packages/backend/Dockerfile -t cookiepal-backend .
docker run -d --name cookiepal-backend \
--restart=always \
-p 8080:8080 \
-e SITE=https://example.com \
-e ORIGINS=https://example.com,https://www.example.com \
-e DATABASE_URL=postgres://user:pass@db-host:5432/cookiepal \
cookiepal-backendOr docker compose up -d from packages/backend/ (uses the co-located docker-compose.yml).
Railway
- New project → deploy from GitHub → this repo.
- Root Directory blank; Dockerfile Path
packages/backend/Dockerfile. - Add a Postgres plugin, copy its
DATABASE_URLinto the backend service's env. - Set
SITE=https://yoursite.comandORIGINS=https://yoursite.com. - Deploy. Use the generated public URL as
backendURLin your banner config.
Fly.io
fly launch --dockerfile packages/backend/Dockerfile --no-deploy
fly postgres create --name cookiepal-db
fly postgres attach cookiepal-db # sets DATABASE_URL
fly secrets set SITE=https://yoursite.com ORIGINS=https://yoursite.com
fly deployYour own VPS
Any Postgres + the Docker command above. Put Caddy / nginx in front for TLS:
backend.yoursite.com {
reverse_proxy :8080
}API
POST /consent
Public endpoint. Called by the browser SDK.
// Request
{
"consentId": "uuid",
"consent": { "necessary": true, "analytics": false }
}
// 201 Created
{ "ok": true }Server-stamped on insert: id, site (from SITE), ip, user_agent, country, region, created_at. Unknown body fields are stripped. JSON-only (rejects non-application/json with 415). Payloads are size-capped.
Geo headers recognised (first match wins): cf-ipcountry, x-vercel-ip-country, x-amz-cf-ipcountry, cloudfront-viewer-country for country; cf-region, x-vercel-ip-country-region for region. If none are present, both stay NULL.
GET /health
Returns {"status":"ok"}. No auth.
Schema
CREATE TABLE consents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site TEXT NOT NULL,
consent_id TEXT NOT NULL,
consent JSONB NOT NULL,
ip TEXT,
user_agent TEXT,
country TEXT,
region TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_consents_site_created_at ON consents (site, created_at DESC);
CREATE INDEX idx_consents_consent_id ON consents (consent_id);Append-only. Latest state for a given consentId:
SELECT * FROM consents
WHERE consent_id = $1
ORDER BY created_at DESC
LIMIT 1;Rate limiting
Not built-in. Put the backend behind Cloudflare / nginx / a load balancer for rate limiting, TLS, and DDoS protection.
Programmatic use
import { startServer } from '@cookiepal-oss/backend';
const handle = await startServer({
site: 'https://example.com',
origins: ['https://example.com'],
port: 8080,
dbUrl: 'postgres://user:pass@host:5432/cookiepal',
logLevel: 'info',
});
// later…
await handle.stop();License
MIT. See repo root.
