Next.js and Supabase, where the data layer is the product.
A monorepo where the app and the background jobs are just consumers of a data layer. Every query and every authorization check lives there, with Postgres and deny-by-default RLS as a backstop.
Use it when
You're starting a Postgres SaaS that needs background jobs, and you want authorization in server code where it can be reviewed — not scattered across SQL policies.
On MongoDB instead of Postgres? template-next-mongo instead →What's included
Done from the start.
The data layer
@ketryon/db holds both Supabase clients, every DTO and every query. Every function takes a session, never a user id.
RLS as a second lock
Deny-by-default on every table, with pgTAP tests. Reads go under RLS, writes through the data layer.
Background jobs
Trigger.dev tasks on named queues with concurrency limits, and every run saved to a job_run table.
An example to build from
An orders feature with a page, server actions, an API route and token-refreshing middleware.
Lint as a guard
ESLint stops the app from importing the Supabase client or the jobs' privileged entry point.
Types from the database
pnpm db:types generates the types from the schema after every migration.
The principles
The rules it keeps.
- 1
The index file exports no client. A new query goes into the data layer, where it gets reviewed.
- 2
Every data-layer function takes a session, never a user id.
- 3
getUser(), never getSession(), for anything security-relevant.
- 4
Middleware is for redirects, not authorization — assume it can be bypassed.
- 5
Keyset pagination with a cap. No OFFSET.
- 6
Every job saves its outcome, because a vendor dashboard only remembers a few weeks.
Get started
pnpm install
cp .env.example apps/web/.env.local
cp .env.example packages/jobs/.env
pnpm db:start # local Supabase (needs Docker)
pnpm db:reset # migrations + seed
pnpm db:types
pnpm dev
pnpm jobs:dev # separate terminalStructure
supabase/ migrations, seed, pgTAP RLS tests
packages/db/ @ketryon/db — Supabase clients, DTOs, every query
packages/jobs/ @ketryon/jobs — Trigger.dev tasks
apps/web/ Next.js 16 — calls the DAL, owns no database codeEnvironment variables
- NEXT_PUBLIC_SUPABASE_URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- TRIGGER_PROJECT_REF
- TRIGGER_SECRET_KEY
Copy .env.example into the app and into the jobs package. The service-role key is server-only.
Good to know
- The SQL — migrations, seed and the pgTAP tests — hasn't been run against a real database here. Run pnpm db:start, db:reset, db:test and db:lint before trusting it.
- Type-check and lint pass with Next 16.4, React 19.3 and Zod 4.6.
Built with
- Next.js 16
- React 19
- Supabase
- Postgres
- Trigger.dev
- Zod 4
- Tailwind CSS 4
- pnpm workspaces
More templates
template-next-mongo
MITNext.js and MongoDB, where a shared data layer owns the client, schemas, queries and authorization.
Next.js 16 · React 19 · MongoDB driver 7
Read more →template-monorepo
MITA Next.js web app, an Express API and Inngest background jobs sharing one database schema.
Next.js 15 · React 19 · Clerk
Read more →template-backend
MITA REST API in Express and TypeScript with sign-in, Postgres, Redis and validation in place.
Express 5 · TypeScript · Clerk
Read more →template-gateway
MITA Next.js layer between your apps and an existing API, keeping the API key on the server.
Next.js 15 · React 19 · TypeScript
Read more →template-react-native
MITAn iOS and Android app in Expo with navigation, light and dark themes and a typed API client.
Expo SDK 54 · React Native 0.81 · React 19
Read more →
Let's build
the next thing.
You talk to me, not a salesperson. The first call is free.
— Kenny
psst, it takes 15 min
Write, and I reply the same working day.