STACK IT FAST
ALL RULES & SKILLS

SvelteKit + PostgreSQL + Drizzle ORM

Raw .MD sveltekit-postgres-drizzle
CURATED RULE AGENTS.MD + CLAUDE.MD + .MDC + SKILL.MD

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

AGENTS.md
Paste in your project root
SKILL.md
Installs to .claude/skills/sveltekit-postgres-drizzle
INTERACTIVE RULE & SKILL VIEWER (AGENTS.MD, CLAUDE.MD, .MDC & SKILL.MD)
Optimized for:CursorClaude CodeWindsurfAGY
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.
ARCHITECTURE NOTES & IMPLEMENTATION GUIDE
Export as Markdown

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.

MORE AI AGENT CODING RULES & SKILLS
View All Rules & Skills