An sophisticated boiler-plate built for simplicity.
Carlo's starter for making a SvelteKit app with batteries included on stuff I like after experimenting for years.
This is handcrafted from my own research. This might not work for you, but it works for me. π€
You can also try my other starters:
- π Solid Launch - If you like Solid, but same robust practices.
- π Simple and minimal - SvelteKit is the best JS framework for simplicty and getting the job done. Hands-down.
- β‘οΈ Super-fast dev server - way faster than NextJS thanks to Vite. You need to feel it to believe it! It can also literally build your app in seconds.
- βοΈ Selfhost-ready - Crafted with simple hosting in mind that'll still probably scale to millions. Just spin up Docker container on a good'ol VPS without locking into serverless. DHH and Shayan influenced me on this. You can still host it on serverless tho. I think? lol
- π Batteries-included - took care of the hard stuff for you. A well-thought-out folder structure from years of making projects: a design system, components, utilities, hooks, constants, an adequate backend DDD-inspired sliced architecture that isn't overkill, dockerizing your app, and most importantly---perfectly-crafted those pesky config files.
- π Authentication-Ready - One thing devs get stuck on. There's a practical auth implemented from scratch here that doesn't vendor-lock you into any auth provider.
- Password
- Transactional Emails (Forgot Password, Email Verification)
- OAuth
- Magic Link
- User Management Dashboard
- Bun - Runtime and package manager. You can always switch to Node and PNPM if you wish.
- Svelte - Frontend framework that I like. Pretty underrated, but awesome!
- SvelteKit - Like NextJS, but for Svelte and Vite! Simpler and Faster!
- tRPC - E2E typesafety without context switching. Just amazing DevX.
- Tailwind - Styling the web has been pretty pleasant with it. I even use it on React Native for work. It's amazing.
- Prisma - Great migrations workflow, but I want to maximize perf.
- Kysely - Great typesafe query builder for SQL, minimally wraps around db connection.
- SQLite/LibSQL (Turso) - Cheapest database, easy to use.
- Lucia - Makes self-rolling auth easy.
- SES or MimePost - Emails
- Backblaze - Cheap blob object storage with an S3-compatible API.
- Paddle - Accept payments and pay foreign taxes.
I'll assume you don't want to change anything with this setup after cloning so let's get to work!
-
Copy the environment variables
cp .env.example .env
-
Replace the
<absolute_url>
in the local database with:pwd # If it outputs: /User/Projects/svelte-launch # Replace the .env with: DATABASE_URL="file:/User/Projects/svelte-launch/local.db"
-
Generate
bun db:generate # generates Kysely and Prisma client types. bun db:migrate # migrates your database.
-
Install deps and run dev
bun install bun dev
I took care of the painstaking parts to help you develop easily on a SPA + SSR + backend paradigm. You can take take these practices to different projects as well.
-
Make use of the
code-snippets
I added. It'll help! -
Check all typescript errors (
Cmd
+Shift
+B
>tsc:watch tsconfig.json
). -
Authentication Practices - I have these out-of-the-box for you so you won't have to build it.
-
Getting Current User.
import { authStore } from '@/stores/auth.store';
-
Login, Logout, Register
import { login, logout, register } from '@/stores/auth.store';
-
Hydrating Current User
This will also automatically hydrate in your layouts. Anywhere you use
$authStore
, it's magic.// page.server.ts export async function load(event: PageServerLoadEvent) { const trpcClient = initTRPCSSRClient(event.request.headers, event.setHeaders); const result = await trpcClient.auth.currentUser.query(); if (!result.user) { throw redirect(302, '/dashboard'); // Must be a public route here. } return { user: result.user ?? null, }; } // page.svelte import { authStore, hydrateAuthStore } from '@/stores/auth.store'; let { data } = $props(); hydrateAuthStore(data.user);
-
Protecting Routes (Client-Side) - Just block the content.
<script> import ProtectedRoute from '@/components/common/protected-route.svelte'; </script> <ProtectedRoute> On the server (hydration), this part will not be rendered if unauthenticated. On the client, you will be redirected to a public route if unauthenticated. </ProtectedRoute>
-
Protecting Routes (SSR) - Automatically redirect.
import { initTRPCSSRClient } from '@/lib/trpc-ssr-client.js'; import { redirect } from '@sveltejs/kit'; import type { PageServerLoadEvent } from './$types'; export async function load(event: PageServerLoadEvent) { const trpcClient = initTRPCSSRClient(event.request.headers, event.setHeaders); const result = await trpcClient.auth.currentUser.query(); if (!result.user) { throw redirect(302, '/dashboard'); // Must be a public route here. } return { user: result.user ?? null, }; }
-
-
Dataloading Practices - Also have these out-of-the-box for most usecases since they're tricky to do if you're clueless:
- Tanstack Query (Client-only) - Use
trpc-client.ts
- Hydrated Tanstack Query (SSR) - Use
create-dehydrated-state.ts
+trpc-ssr-client.ts
- Tanstack Query (Client-only) - Use
My backend architecture is inspired by DDD where I separate in layers, but I keep it pragmatic by not going too overkill with Entities, Domains, and Aggregates. I personally still like the anemic data-driven architecture for most of my apps since the apps I make aren't too business-logic-heavy.
.
βββ server/ # - root
βββ dao/ # - data-access-objects
β βββ *.dao.ts
βββ modules/
β βββ <module>/
β βββ services/
β β βββ *.service.ts # 1 service usecase
β βββ <module>.controller.ts
βββ _app.ts # - root TRPC router.
dao
- abstracted away all queries here to interface with them as plain functions. It actually helps me mentally collocate db queries from service logic when I'm using them inside the service.modules
- a vertical slice of each module-group. This just depends on how I feel about grouping them. You get better overtime.<module>.controller.ts
- pretty much a group of http endpoints. I can put the DTOs/Validations for each endpoint here without context-switching.services
- these are even smaller pieces of logic that can be used inside each endpoint. It's not necessary to use if the app isn't too big, but it helps._app.ts
- The root trpc router where theAppRouter
type is exported.
Warning
Still in progress
Here are some guides on how to deploy.
- Kamal (self-host VPS - I recommend)
- Dokku (self-host VPS)
- Caprover (self-host VPS)
- Cloudflare (serverless + static)
- Vercel (serverless + static)
- Netlify (static)
I'll probably make a swapping guide soon. To replace to these:
- Runtime: Bun -> Node
- Package Manager: Bun -> PNPM
- ORM: Prisma -> Drizzle
- Database: SQLite -> PostgreSQL, CockroachDB, MongoDB