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:
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:
trpc-client.ts
create-dehydrated-state.ts
+ trpc-ssr-client.ts
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 the AppRouter
type is exported.[!WARNING]
Still in progress
Here are some guides on how to deploy.
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