Architecture
How the Sereal app, database, and external services fit together.
Sereal is a single Next.js application talking to a Supabase project and a small set of optional external APIs. There is no separate backend service — server work happens inside Next.js (server components, server actions, and a handful of API routes).
Components
| Piece | Role |
|---|---|
| Next.js app (App Router, TypeScript) | The whole product — UI, server actions for writes, API routes for cron and exports. Runs as one Docker container on port 3000. |
| Supabase Postgres | All durable data. Row-Level Security scopes every tenant's rows by auth.uid(). |
| Supabase Auth | Email/password login. The owner account is created by the bootstrap script. |
| Supabase Storage | Item photos, in a private sereal-images bucket. |
| eBay Browse API | Optional. Powers deal polling, Scout, and sold-comp lookups. |
| Email (Resend or SMTP) | Optional. Sends the daily deal digest. |
How a request flows
- Reads are server components querying Supabase directly with the request's authenticated session. Database views (e.g. the dashboard rollup and the grading-pipeline view) do the heavy aggregation in Postgres.
- Writes go through server actions that self-scope to the current tenant and call atomic RPCs for anything multi-step (recording a sale, finalizing a grade, allocating a break's cost). Defense-in-depth: actions scope by tenant even though RLS would also catch it.
- Scheduled work (deal polling, the email digest, value-chart snapshots)
runs through
/api/cron/*routes guarded by aCRON_SECRETheader, triggered by an external scheduler you control.
Database migrations are the source of truth
The schema is defined by the ordered migration files in
supabase/migrations/,
applied with supabase db push --linked. Migrations are immutable once applied —
changes always land as a new migration. See Data model
for the entity overview and check:migrations
for verifying parity.
Design decisions worth reading
The non-obvious "why" behind the operational model is captured in Architecture Decision Records:
- ADR-001 — External API economics: why operators bring their own eBay credentials rather than sharing a Sereal key.
- ADR-002 — Tenant cardinality: the schema is multi-tenant, but the self-host flow assumes one operator, one collection.
- ADR-003 — Self-hosted distribution: the config-flag model
(
SELF_HOSTED, signup, billing) and how cloud vs. self-host behavior diverges.
All three live in
docs/architecture-decisions/.