Launch production-ready SvelteKit apps faster — Svelte 5 runes, Remote Functions, Arctic/Lucia OAuth, Drizzle ORM, Redis, and Tailwind v4/shadcn — all wired for a Docker-first DX.
Modern SvelteKit 2 starter kit for building SaaS apps with:
This repository is designed as a template to bootstrap production-ready SvelteKit apps with batteries included, strong defaults, and a clean architecture.
Note: Some logos (e.g., Arctic) may not be on Simple Icons; we include Lucia/OpenID as auth stand-ins.
This template follows a conventional SvelteKit layout and keeps server-only logic out of the client bundle.
src/routes
— app routes (pages, endpoints)src/lib
— shared code (components, hooks, utils, constants)src/lib/remotes
— Remote Functions definitions (server-only)src/lib/server
— server-only modules (DB, Redis, auth, services)src/lib/server/auth
— Arctic OAuth setup (providers, session utils)src/lib/server/db
— Drizzle schema and clientsrc/lib/server/redis
— Redis client (ioredis)src/lib/server/services
— domain-specific server servicessrc/lib/shared
— isomorphic utilities/constants/schemasdrizzle/
— migration filesstatic/
— static assets.
├─ src/
│ ├─ routes/ # Pages & endpoints
│ │ ├─ +layout.svelte # Root layout
│ │ ├─ +layout.server.ts # Root server load (e.g., session)
│ │ ├─ +page.svelte # Landing page
│ │ ├─ auth/
│ │ │ ├─ login/
│ │ │ │ ├─ +page.svelte # Login UI
│ │ │ │ └─ +page.server.ts # Login server action/loader
│ │ │ └─ callback/[provider=auth_provider]/
│ │ │ └─ +server.ts # OAuth callback endpoint
│ │ └─ dashboard/ # Example protected area
│ │ ├─ +layout.svelte
│ │ ├─ +layout.server.ts
│ │ └─ +page.svelte
│ ├─ lib/
│ │ ├─ assets/ # Images/icons used by components
│ │ ├─ components/ # UI components (shadcn-svelte, layouts)
│ │ ├─ hooks/ # Utility hooks (Svelte 5 runes aware)
│ │ ├─ remotes/
│ │ │ └─ auth/auth.remote.ts # Remote Functions (login/logout)
│ │ ├─ server/ # Server-only modules
│ │ │ ├─ auth/
│ │ │ │ ├─ core/ # Auth core types/config/instance
│ │ │ │ ├─ providers/ # Google/GitHub providers
│ │ │ │ ├─ sessions/ # Redis session manager
│ │ │ │ └─ index.ts # Auth entry point
│ │ │ ├─ db/ # Drizzle client & schema
│ │ │ ├─ middlewares/ # Route/server middlewares/guards
│ │ │ ├─ redis/ # Redis client (ioredis)
│ │ │ └─ services/ # Domain services (server-only)
│ │ ├─ shared/ # Isomorphic utilities/constants/schemas
│ │ │ ├─ constants/
│ │ │ ├─ schemas/
│ │ │ └─ utils/
│ ├─ params/ # Route param matchers (e.g., auth_provider)
│ │ ├─ auth_provider.ts
│ │ ├─ numeric.ts
│ │ ├─ slugable.ts
│ │ └─ uuid.ts
│ ├─ hooks.server.ts # Init + set user in locals from session
│ └─ app.d.ts # App Locals/types
├─ drizzle/ # SQL migrations
├─ docker/
│ └─ docker-compose.dev.yml # Postgres + Redis for local dev
├─ static/ # Static assets
├─ components.json # shadcn-svelte config (aliases)
├─ Makefile # Docker helpers (make up/down/dev/...)
├─ .env.example # Example env vars (copy to .env)
└─ README.md # This file
Note: exact folders may evolve; the separation between client-safe and server-only code is intentional to keep secrets and heavy deps off the client.
You do NOT need to install PostgreSQL or Redis locally; this starter runs them in Docker for you.
First time setup (build containers once, start services, and run the app):
git clone <this-repo-url> my-app
cd my-app
bun install # package manager is Bun (use `bun` for scripts)
cp .env.example .env # then fill in environment variables (see below)
# Build service images (Postgres, Redis) and create containers
make build
# Start Postgres & Redis, then run the app (Bun by default)
make dev
Open http://localhost:5173 (or the port printed in your terminal).
Start here:
make dev
The dev
target will:
make check
)Other useful targets:
make build # build service images (first run or when docker files change)
make up # start Postgres & Redis in the background
make down # stop and remove containers
make restart # restart containers
make logs # follow logs for all services
make redis-flush # flush all Redis data (dev only)
Suggested local env values when using Docker services:
DATABASE_URL=postgres://postgres:123@localhost:5432/starter
REDIS_URL=redis://localhost:6379
Note: make dev
uses Bun (DEV_COMMAND in Makefile). You can still run npm run dev
if you prefer Node/npm; just ensure make up
has started the services first.
Use a local .env
(copy from .env.example
) and set the following. Names are UPPERCASE by convention.
Required for core app:
DATABASE_URL
— PostgreSQL connection stringpostgresql://postgres:123@localhost:5432/starter
src/lib/server/db/index.ts
, drizzle.config.ts
REDIS_URL
— Redis connection stringredis://localhost:6379
src/lib/server/redis/redis.ts
AUTH_SECRET
— long random string to sign the auth session cookieopenssl rand -base64 32
or bunx nanoid
OAuth (enable providers you use):
GOOGLE_CLIENT_ID
, GOOGLE_CLIENT_SECRET
GITHUB_CLIENT_ID
, GITHUB_CLIENT_SECRET
Dev helper:
MOCK_LOGIN
— if true
, enables mock login flows for local development only, disabling the OAuth.SvelteKit env access:
$env/dynamic/private
(used in this repo)$env/static/private
$env/static/public
is not used here)Security:
src/lib/server/**
).env
; commit .env.example
to document variablessrc/lib/db/schema.ts
src/lib/db/client.ts
drizzle/
Common scripts:
npm run db:generate # generate SQL from schema
npm run db:migrate # apply migrations
npm run db:studio # optional: open drizzle studio if configured
Note: drizzle-kit migrate
does not create the database itself. Ensure the target database exists before running migrations. If you use the provided Docker setup, create it once with:
docker exec -it starter-postgres psql -U postgres -c "CREATE DATABASE starter;"
Use transactions for multi-step writes and create indexes for critical queries. See .windsurf/docs/drizzle.txt
for detailed Drizzle notes.
Use Redis for:
Create a single connection in src/lib/server/redis/redis.ts
and reuse it across server modules.
This project implements OAuth using the Arctic library (OAuth 2.0/OpenID; state, nonce, and PKCE where supported). For Arctic docs, see: https://arcticjs.dev/
Why this approach (and not Better Auth):
Implementation overview:
src/lib/server/auth/providers/{google,github}.ts
src/lib/server/auth/core/*
, src/lib/server/auth/index.ts
src/lib/server/auth/sessions/redis-session.ts
(signed cookie + Redis)src/lib/remotes/auth/auth.remote.ts
(login/logout)src/params/auth_provider.ts
src/hooks.server.ts
(loads event.locals.user
from session)Customize or extend:
src/lib/server/auth/providers/<provider>.ts
, export it, and register in src/lib/server/auth/index.ts
; set <PROVIDER>_CLIENT_ID/_SECRET
envsindex.ts
Flow in this repo (simplified):
auth.handleLogin
builds provider URL with callback ${origin}/auth/callback/<provider>
and redirectsauth.handleLogout
clears Redis state and deletes cookieRefer to Arctic docs for provider specifics and advanced flows.
secure
cookies and appropriate SameSite
httpOnly
, sameSite=lax
, secure
(prod); short TTLs for sensitive cookiesAUTH_SECRET
if compromised; invalidate sessions in Redis on rotationstate
/nonce
and enforce PKCE where supported${ORIGIN}/auth/callback/<provider>
(example: http://localhost:5173/auth/callback/myprovider
)<PROVIDER>_CLIENT_ID
and <PROVIDER>_CLIENT_SECRET
and add them to .env
src/lib/server/auth/providers/<provider>.ts
src/lib/server/auth/providers/index.ts
src/lib/server/auth/index.ts
(see example above)<provider>
to the param matcher in src/params/auth_provider.ts
so callbacks are acceptedsrc/lib/shared/constants/enum.ts
(AuthProvider
). This keeps client code (UI, forms) in sync with server providers.{ provider: '<provider>' }
make dev
, complete the OAuth flow, and confirm a session is createdNotes:
You can replace Redis-backed sessions with your own storage by implementing a new manager.
Steps:
src/lib/server/auth/sessions/<name>-session.ts
BaseSession
and implement: getSession
, setSession
, deleteSession
BaseSession
(name, httpOnly
, sameSite
, secure
)src/lib/server/auth/index.ts
, import your manager and pass an instance to AuthInstance
We use SvelteKit Remote Functions to call server code from the client safely and with types, without hand-rolling endpoints.
src/lib/remotes/*
(server-only code that never ships to the browser).See .windsurf/docs/sveltekit.txt
for a concise reference to SvelteKit 2 Remote Functions.
Usage:
src/app.css
and component-level styles minimalMIT — see LICENSE
.