Skip to content
Articles / Development

My Web App Stack

RS
Randall Sutton
6 min read

My other article covers the stack I use for content sites. This one covers what I use when building full-stack web applications — specifically Resolve, the service platform that powers Devclock.

Resolve handles client projects, ticketing, contact forms, uptime monitoring, AI agents, and more. It’s a real app with real users, and the stack reflects that.

Framework: SvelteKit + Svelte

Same foundation as my content sites, but used very differently. SvelteKit handles both the frontend and the API layer — all the REST endpoints live as +server.ts files alongside the UI routes. No separate backend service to maintain.

Svelte’s runes ($state, $derived, $effect) make state management straightforward. Combined with Supabase’s real-time subscriptions, I get reactive UIs that stay in sync across tabs and users without much ceremony.

The app is organized by feature domain — /projects, /tickets, /clients, /forms, /uptime — each with its own routes, components, and page state.

Database: Supabase (PostgreSQL)

Supabase is the backend. It gives me PostgreSQL with a few critical extras:

  • Auth — JWT-based authentication with role-based access control. Internal users vs. external clients, session management with cookies, all handled through Supabase’s auth layer.
  • Real-time — Subscriptions that push database changes to connected clients. Combined with optimistic updates on the frontend, the UI stays responsive.
  • Storage — File uploads up to 50MB without building a separate file service.
  • Cronpg_cron for scheduled database jobs.

No ORM. I use Supabase’s client SDK with TypeScript types generated directly from the database schema. The types stay in sync with the actual tables, so if a column changes, the compiler catches it.

There are database migrations tracking the schema over time, all version controlled.

AI: Anthropic Claude

Resolve has deep AI integration using the Anthropic SDK. There’s a custom tool registry system that gives Claude access to projects, tickets, and tasks — so the AI can actually perform actions in the app, not just answer questions.

The AI agent runner manages context (current project, ticket, task) and tool execution. It’s one of the most interesting parts of the codebase and something I’m actively expanding.

Styling: Tailwind CSS + Bits UI

Same Tailwind setup as my content sites, but with Bits UI for headless components — dropdowns, dialogs, popovers, and other complex UI patterns that need proper accessibility and keyboard handling.

I use Tailwind Merge and Tailwind Variants for composing utility classes, and Lucide for icons throughout the app.

API Design

All API endpoints are SvelteKit server routes. There’s a service layer pattern — email.service.ts, conversation.service.ts, comment.service.ts — that keeps business logic separate from route handlers.

Authorization is handled in hooks.server.ts, which checks roles and restricts internal-only routes before they execute.

Email: Resend

Resend handles transactional email delivery. Each company in the system can have its own API key, so emails come from the right domain. There’s a templates service for building consistent email content.

Background Jobs

Vercel cron triggers hit /api/jobs/process every minute. The job queue system picks up pending work — sending emails, processing webhooks, running uptime checks — and executes it. Simple, reliable, and no separate worker infrastructure to manage.

Hosting: Vercel

Same as my content sites — Vercel with @sveltejs/adapter-vercel. But for an app like Resolve, Vercel is doing more than serving static files. It’s running server-side routes, API endpoints, and cron jobs.

Security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy) are configured in vercel.json.

Testing: Vitest + Playwright

  • Vitest for unit tests with jsdom for DOM testing.
  • Playwright for end-to-end tests with auth state management — tests log in once and reuse the session.

Both run in CI via GitHub Actions alongside lint and type checks.

TypeScript Everywhere

Strict mode TypeScript across the entire codebase. Database types are generated from the schema, API responses are typed, component props use Svelte’s typed $props(). The compiler catches most issues before anything hits a browser.

Why This Stack

The theme is the same as my content site stack: minimize the number of things I have to think about. SvelteKit handles both frontend and backend. Supabase handles auth, database, real-time, and storage. Vercel handles deployment and cron. TypeScript holds it all together.

There’s no microservices architecture, no separate API server, no container orchestration. It’s one SvelteKit app, one Supabase project, and one Vercel deployment. That simplicity is intentional — it lets me move fast and ship features instead of managing infrastructure.