Technology
Data & backend
Postgres, Drizzle ORM, API design, and how we structure server-side work.
Last updated 2026-04-27
The backend is where the system's behavior lives. We keep it boring on purpose — Postgres + a thin ORM + Next.js API routes covers nearly every shape of work we ship.
Postgres
Postgres is the database of choice for serious work. It scales horizontally with read replicas, vertically with bigger boxes, and handles every workload from simple key-value to full-text search to time-series with reasonable competence.
We host Postgres on Neon by default. Neon is serverless Postgres — you pay for what you use, scales to zero, fast cold starts. For high-traffic apps we'll move to a more traditional managed Postgres (RDS, Supabase, Crunchy) where the economics flip in their favor.
We don't use:
- MongoDB unless there's a hard reason — most "I need a document store" cases are better served by Postgres + jsonb columns.
- DynamoDB unless the team is already on AWS heavy and the access patterns genuinely match.
- Firebase / Firestore — proprietary, lock-in, hard to migrate off.
Drizzle ORM
We use Drizzle for the database layer. Why:
- Type-safe — your schema is TypeScript, your queries are TypeScript, the columns you select come back as the right types.
- No codegen step — the schema file is the source of truth, no generated files to keep in sync.
- Light runtime — closer to a query builder than a heavyweight ORM. The queries you write are basically SQL.
- Migration support that's actually usable —
drizzle-kit generateproduces SQL, you review it, you commit it.
We don't use Prisma. Prisma is fine but Drizzle is faster, lighter, and gives us the SQL layer we want without the overhead.
API design
For a Next.js app the natural choice is server actions (for forms and simple mutations) and API route handlers (for anything called from outside the same app). We use both.
When we expose a public API for third parties to consume:
- REST by default. Clean URLs, standard HTTP verbs, predictable.
- OpenAPI / Swagger spec generated and served at
/api/docsso consumers can self-serve. - Rate limiting at the edge.
- API keys managed in your admin dashboard, with the ability to revoke and rotate.
We rarely build GraphQL APIs. The "GraphQL is universally better" narrative didn't survive the past few years. GraphQL has its place — internal mobile apps with weird data dependencies — but the operational cost is rarely worth it for typical web work.
Auth
Auth is hard to get right and the cost of getting it wrong is severe. We use Clerk for new projects unless there's a specific reason not to.
Clerk gives you sign-in, sign-up, password resets, email verification, MFA, social login, magic links, organizations, role-based access — all production-grade, all maintained by people whose entire job is auth. The alternative is months of building auth ourselves; we'd rather spend that time on your actual product.
When Clerk isn't a fit (legacy systems, on-prem requirements, unusual compliance constraints), we'll evaluate alternatives — Auth0, Stytch, or a custom build on top of Lucia.
Background jobs
Three tiers depending on what's needed:
- Vercel Cron for simple scheduled work (nightly reports, weekly emails). Free, simple, runs in your existing Vercel project.
- Inngest or Trigger.dev for workflow-style jobs that need retries, fan-out, durable state. We've used both; both are good.
- Custom worker on a VM for truly long-running or compute-heavy work. Rare.
Caching
Three layers, in priority order:
- The browser. Aggressive
Cache-Controlheaders,next/imagefor static assets. - The CDN. Vercel's edge cache covers static and ISR content.
- The application. A Redis cache (Upstash by default) for hot data when needed.
We don't reach for caching by default — most apps don't need it. We add it when the load tests say so.
File storage
Vercel Blob for files associated with a Vercel-hosted app. Works seamlessly with Next.js's image optimization and route handlers.
For larger or more access-controlled files we use S3 or Cloudflare R2. R2 specifically when egress costs would be high — R2 has zero egress fees.