Skip to content
STACK IT FAST

SvelteKit + PostgreSQL + Drizzle ORM

Curated rule SaaS / Web App · Developer Tool & API Updated Sep 2026 Which file does my tool read?
sveltekit-postgres-drizzle.md

Production guidelines for SvelteKit form actions and load functions, Drizzle ORM over PostgreSQL, and Svelte 5 runes-based reactivity.

Formats
4 files
AGENTS.md
48 lines
CLAUDE.md
16 lines
Languages
TypeScript
Updated
Sep 2026
Used by
4 projects
Install

Writes .claude/skills/sveltekit-postgres-drizzle/SKILL.md

$ curl -s --create-dirs -o .claude/skills/sveltekit-postgres-drizzle/SKILL.md https://stackitfast.com/rules/sveltekit-postgres-drizzle/SKILL.md

Rule files

AGENTS.md· 48 lines · 3.7 KB
1# Project Architecture & Guidelines (SvelteKit + PostgreSQL + Drizzle)
2
3## 1. System Architecture
4- **Framework**: SvelteKit (Svelte 5, runes-based reactivity `$state`/`$derived`/`$effect`).
5- **Database & ORM**: PostgreSQL with Drizzle ORM (`drizzle-orm/postgres-js`, `drizzle-kit`) — for solo builders and small teams, point `DATABASE_URL` at a managed provider (Supabase or Neon, both have a usable free tier) instead of operating your own Postgres instance; self-host only once you need full infra control.
6- **Rendering**: Server-side rendering by default; `+page.server.ts` for data loading, form actions for mutations.
7- **Validation**: TypeScript strict mode + Zod for form and API input validation.
8
9## 2. Load Functions & Data Fetching
10- Fetch data exclusively in `+page.server.ts` / `+layout.server.ts` `load()` functions — never in `onMount` for data the page needs to render.
11- Return typed data from `load()`; SvelteKit infers `PageData`/`LayoutData` automatically via `./$types`.
12- Use `depends()`/`invalidate()` for fine-grained cache invalidation instead of full page reloads after mutations.
13
14## 3. Form Actions & Mutations
15- All writes go through named form actions in `+page.server.ts` (`export const actions = { create: async ({ request }) => {...} }`), not client-side `fetch` to API routes, unless the mutation is triggered outside a `<form>`.
16- Validate `await request.formData()` with Zod before touching the database; return `fail(400, { errors })` on validation failure so the form can show inline errors without JavaScript.
17- Use progressive enhancement (`use:enhance`) so forms work with JS disabled and get optimistic UI when enabled.
18
19## 4. Database Client & Migrations
20- Singleton Drizzle client in `src/lib/server/db/index.ts`, imported only from `+page.server.ts` / `+server.ts` files — never from `.svelte` components (build fails loudly if `postgres` leaks into the client bundle, which is the intended guardrail).
21 ```typescript
22 import { drizzle } from 'drizzle-orm/postgres-js';
23 import postgres from 'postgres';
24 import { DATABASE_URL } from '$env/static/private';
25 import * as schema from './schema';
26
27 // DATABASE_URL: Supabase/Neon pooled connection string for solo/small-team
28 // deploys — skips operating a Postgres server entirely.
29 const client = postgres(DATABASE_URL, { prepare: false });
30 export const db = drizzle(client, { schema });
31 ```
32- Generate migrations with `drizzle-kit generate`; apply with `drizzle-kit migrate`. Never hand-edit generated SQL.
33
34## 5. Common Pitfalls / Coding Standards
35- ❌ Importing `$lib/server/*` modules from client-reachable code — SvelteKit throws at build time, but only if the import chain is direct; re-exporting through a shared barrel file can hide the leak.
36- ❌ Fetching in `onMount` for data needed on first paint, causing layout shift and losing SSR benefits.
37- ✅ Use `$state`/`$derived` runes for component-local reactivity; avoid the legacy `$:` reactive statement style in new code.
38
39## 6. Testing Conventions
40- Vitest for unit tests on `load()` functions and Drizzle query helpers, with a `pg` test container or Supabase local dev instance.
41- Playwright for e2e coverage of form actions, including the no-JS progressive-enhancement path.
42- Run `svelte-check` and `tsc --noEmit` as required checks — Svelte's type-checking catches prop/slot mismatches that plain `tsc` misses.
43
44## 7. Git Workflow & PR Conventions
45- Conventional Commits scoped to the route, e.g. `fix(routes/projects): validate slug uniqueness in create action`.
46- Generated Drizzle migrations ship in the same PR as the schema change.
47- Require `svelte-check`, `tsc --noEmit`, and `bun run build` green before merge.
48- Squash-merge; never `drizzle-kit push` against a shared or production database from a feature branch.

Works with Cursor · Claude Code · Windsurf · AGY

Architecture notes

Architecture Overview

Standardized production guidelines for SvelteKit (Svelte 5), Drizzle ORM, and PostgreSQL, built around SvelteKit’s server-first data loading and form action model.

Verified Real-World Adoption

The SvelteKit pattern shows up as the frontend layer in production self-hosted tools like Immich (photo management, SvelteKit + PostgreSQL + pgvector) and Open WebUI (AI interface, SvelteKit + FastAPI), with PocketBase built around the lighter-weight Svelte (non-Kit) equivalent.

Key Architectural Nuances

  • Server-Only Import Boundary: SvelteKit enforces at build time that $lib/server/* modules can’t leak into client-reachable code, making credential leaks a build failure instead of a runtime surprise.
  • Progressive Enhancement by Default: Form actions work without JavaScript and are enhanced with use:enhance, unlike SPA patterns that require JS for any interaction to function at all.

Frequently asked questions

Does this AGENTS.md work with Cursor, Claude Code, and Windsurf?

Yes — AGENTS.md is the open, cross-tool standard read by Cursor, Claude Code, Windsurf, and 30+ other agents. A dedicated .mdc file is also included for Cursor's native .cursor/rules format.

Why must the Drizzle client only be imported from server files?

SvelteKit's `$lib/server` convention is a build-time guardrail: if server-only code (like a database client holding credentials) is imported into a module reachable from the client bundle, the build fails. Keeping the import chain strictly server-side avoids accidentally shipping the database connection string to the browser.

Why prefer form actions over client-side fetch for mutations?

Form actions work without JavaScript by default (the browser does a normal form POST), and `use:enhance` progressively upgrades them to an AJAX-like experience with optimistic UI when JS is available. A client-side fetch call has no fallback if JS fails to load or errors, and requires manually wiring a separate +server.ts endpoint.

Do I need to self-host PostgreSQL for this stack?

No — point DATABASE_URL at a managed provider like Supabase or Neon and skip database operations entirely; both have a free tier that's more than enough for a solo project or early-stage small team. Self-hosting Postgres only starts to make sense once you need full control over extensions, backups, or data residency.

Used in production

Explore all stacks
Where this stack fits

Stack It First recommends it at:

Walk the web roadmap