Launch production-ready SvelteKit apps faster — Svelte 5 runes, Remote Functions, Arctic OAuth + Password Auth, Drizzle ORM, Database Sessions, 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: This starter uses database sessions by default for simplicity, but Redis sessions are also available for high-performance scenarios.
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, auth, services, config)src/lib/server/auth — Arctic OAuth + Password auth setup (providers, sessions, plugins)src/lib/server/db — Drizzle schema, client, and modelssrc/lib/server/services — domain-specific server servicessrc/lib/hooks — Svelte 5 runes-based hooks (form state, mobile detection, etc.)src/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)
│ │ │ ├─ form-state.svelte.ts # Advanced form state management
│ │ │ ├─ remote-sumbit-handler.svelte.ts # Remote function submission handling
│ │ │ └─ is-mobile.svelte.ts # Mobile detection hook
│ │ ├─ remotes/
│ │ │ ├─ auth.remote.ts # Auth Remote Functions (OAuth/password)
│ │ │ └─ profile.remote.ts # Profile management remotes
│ │ ├─ server/ # Server-only modules
│ │ │ ├─ auth/
│ │ │ │ ├─ core/ # Auth core types/config/instance
│ │ │ │ ├─ providers/ # Google/GitHub OAuth providers
│ │ │ │ ├─ plugins/ # Password authentication plugin
│ │ │ │ ├─ sessions/ # Database session manager
│ │ │ │ └─ index.ts # Auth entry point
│ │ │ ├─ db/ # Drizzle client, schema & models
│ │ │ ├─ config/ # Environment configuration
│ │ │ ├─ middlewares/ # Route/server middlewares/guards
│ │ │ └─ 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 # PostgreSQL + 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. Redis is optional and only needed if you choose to use Redis sessions instead of the default database sessions.
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 (PostgreSQL, Redis) and create containers
make build
# Start PostgreSQL, 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 PostgreSQL & 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 # Optional, only if using Redis sessions
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/startersrc/lib/server/db/index.ts, drizzle.config.tsAUTH_SECRET — long random string to sign the auth session cookieopenssl rand -base64 32 or bunx nanoidOptional (for Redis sessions):
REDIS_URL — Redis connection string (only needed if using Redis sessions)redis://localhost:6379src/lib/server/auth/sessions/redis-session.tsOAuth (enable providers you use):
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETGITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETDev 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.tssrc/lib/db/client.tsdrizzle/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.
This starter currently uses database-backed sessions by default but also includes Redis session support as an alternative. You can easily switch between them.
Sessions are stored in the sessions table with automatic cleanup of expired sessions.
Key benefits:
Redis-backed sessions are also available for high-performance scenarios:
To switch to Redis sessions, uncomment the Redis session manager in src/lib/server/auth/index.ts and set the REDIS_URL environment variable.
Session data includes user ID, IP address, user agent, and expiration time for security tracking.
This project implements a comprehensive authentication system with both OAuth and password-based authentication:
OAuth providers using the Arctic library (OAuth 2.0/OpenID; state, nonce, and PKCE where supported). For Arctic docs, see: https://arcticjs.dev/
Built-in password authentication using bcryptjs with secure hashing and validation.
Why this approach:
Implementation overview:
src/lib/server/auth/providers/{google,github}.tssrc/lib/server/auth/plugins/password.tssrc/lib/server/auth/core/*, src/lib/server/auth/index.tssrc/lib/server/auth/sessions/database-session.ts (signed cookie + database)src/lib/remotes/auth.remote.ts (OAuth/password login/logout)src/lib/server/db/models/user.tssrc/params/auth_provider.tssrc/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 envssrc/lib/server/auth/plugins/<plugin>.ts with login/register methodsindex.tsAuthentication flows:
OAuth Flow:
handleProviderLogin builds provider URL with callback ${origin}/auth/callback/<provider> and redirectshandleLogout clears database session and deletes cookiePassword Flow:
handlePluginLogin validates email/password against databaseMock Login (dev only):
handleMockLogin creates a test user for developmentRefer to Arctic docs for provider specifics and advanced flows.
secure cookies and appropriate SameSitehttpOnly, sameSite=lax, secure (prod); short TTLs for sensitive cookiesAUTH_SECRET if compromised; invalidate sessions in database 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 .envsrc/lib/server/auth/providers/<provider>.tssrc/lib/server/auth/providers/index.tssrc/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 database-backed sessions with your own storage (Redis, file system, etc.) by implementing a new manager.
Steps:
src/lib/server/auth/sessions/<name>-session.tsBaseSession and implement: getSession, setSession, deleteSessionBaseSession (name, httpOnly, sameSite, secure)src/lib/server/auth/index.ts, import your manager and pass an instance to AuthInstanceThe current DatabaseSession implementation provides a good reference for the interface.
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).The starter includes a comprehensive response system (src/lib/shared/utils/remote-response.ts) with standardized response types:
RemoteResponse.success() - Success with dataRemoteResponse.failure() - Failure with error detailsRemoteResponse.go() - Redirect responseRemoteResponse.fail() - SvelteKit fail responseRemoteResponse.redirect() - SvelteKit redirectRemoteResponse.error() - SvelteKit errorThe remoteSubmitHandler hook (src/lib/hooks/remote-sumbit-handler.svelte.ts) provides:
processing reactive variableExample usage:
const submitHandler = remoteSubmitHandler({
onSubmit: async ({ signal, cancel, toast }) => {
return () => handleLogin({ email, password });
},
onSuccess: (data) => {
// Handle success
},
onFailure: (error) => {
// Handle failure
}
});
The starter includes a powerful form state management system built with Svelte 5 runes and Zod v4 validation.
createFormState (src/lib/hooks/form-state.svelte.ts) provides:
aria-invalid attributesExample usage:
const formState = createFormState({
schema: z.object({
email: z.string().email(),
password: z.string().min(8)
}),
initial: { email: '', password: '' },
attribute: {
email: createAttribute({ type: 'email', required: true }),
password: createAttribute({ type: 'password', required: true })
}
});
hasErrors, canSubmit, hasTouchedaddErrors, setErrors, resetErrorsetValue with optional validationupdateSchema for dynamic formsUsage:
src/app.css and component-level styles minimalMIT — see LICENSE.