Skip to content

Blankeos/svelte-launch

Repository files navigation

🧑 Svelte Launch

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. πŸ€“

Benefits

  • 🐭 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.

Tech Stack

  • 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.
  • Stripe, Payrex, or Xendit - Accept payments.

You can also easily swap the database if you want.

  • Postgres - powerful relational DB. (TBD)
  • CockroachDB - serverless database. (TBD)
  • MongoDB - cheap easy to use database. (TBD)

QuickStart

I'll assume you don't want to change anything with this setup after cloning so let's get to work!

  1. Copy the environment variables

    cp .env.example .env
  2. 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"
  3. Generate

    bun db:generate # generates Kysely and Prisma client types.
    bun db:migrate # migrates your database.
  4. Install deps and run dev

    bun install
    bun dev

Useful Development Tips

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.

  1. Make use of the code-snippets I added. It'll help!

  2. Check all typescript errors (Cmd + Shift + B > tsc:watch tsconfig.json).

  3. 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.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.currentUser.query();
      
         if (!result.user) {
            throw redirect(302, '/dashboard'); // Must be a public route here.
         }
      
         return {
            user: result.user ?? null,
         };
      }
  4. 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

Backend Architecture

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.

Deployment

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)

Future Plans

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

About

πŸ… My SvelteKit + tRPC boilerplate starter

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published