sveltekit-trpc-frontend-template Svelte Themes

Sveltekit Trpc Frontend Template

A simple full stack project kickstarter to start any full stack project in just few minutes

SvelteKit 5 + tRPC + Drizzle + Better Auth Starter Template

A production-ready fullstack TypeScript starter with end-to-end type safety, authentication, and a PostgreSQL database.


1. Stack

Layer Library Version Purpose
Framework SvelteKit 5 ^2.57 Routing, SSR, file-based pages
Language TypeScript ^6 End-to-end type safety
Styling Tailwind CSS v4 ^4.2 Utility-first CSS
UI Components shadcn-svelte (bits-ui) ^1.2 Accessible, composable components
ORM Drizzle ORM ^0.45 Type-safe SQL queries
Database PostgreSQL Relational database (Docker locally)
Auth Better Auth ~1.4 Sessions, email/password, GitHub OAuth
API tRPC v11 ^11.16 End-to-end typesafe RPC
tRPC adapter trpc-sveltekit ^3.6 SvelteKit-native tRPC client factory
Serialization superjson ^2.2 Dates, Maps, Sets over the wire
Validation Zod v4 ^4.3 Input schemas for tRPC procedures
Runtime Bun Package manager + script runner
Linter/Formatter Biome ^2.4 Fast lint + format, replaces ESLint/Prettier

2. Setup

# 1. Install dependencies
bun install

# 2. Copy and fill in environment variables
cp .env.example .env

# 3. Start the local PostgreSQL database
bun run db:start

# 4. Apply database migrations
bun run db:migrate

# 5. Start the dev server
bun run dev

Environment variables (.env)

DATABASE_URL="postgres://root:mysecretpassword@localhost:5432/local"
ORIGIN="http://localhost:5173"
BETTER_AUTH_SECRET=""   # generate: openssl rand -base64 32
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""

For GitHub OAuth, create an app at https://github.com/settings/developers with callback URL http://localhost:5173/api/auth/callback/github.


3. Project Structure

.
├── drizzle/                        # Auto-generated SQL migration files
├── docker/
│   └── compose.yaml                # Local PostgreSQL via Docker
├── src/
│   ├── app.d.ts                    # SvelteKit Locals type augmentation (user, session)
│   ├── app.html                    # Root HTML shell
│   ├── hooks.server.ts             # Better Auth session population + request handler
│   └── lib/
│       ├── auth-client.ts          # Browser-side Better Auth client (signIn, signUp…)
│       ├── utils.ts                # cn() helper (clsx + tailwind-merge)
│       ├── components/ui/          # shadcn-svelte components
│       ├── server/
│       │   ├── auth.ts             # Better Auth server config
│       │   ├── db/
│       │   │   ├── index.ts        # Drizzle client (postgres.js)
│       │   │   ├── schema.ts       # App tables + re-exports auth.schema
│       │   │   └── auth.schema.ts  # Better Auth tables (auto-generated)
│       │   └── trpc/
│       │       ├── context.ts      # Per-request context (session + user)
│       │       ├── router.ts       # initTRPC, publicProcedure, protectedProcedure
│       │       ├── index.ts        # appRouter + AppRouter type
│       │       └── routers/
│       │           ├── auth.router.ts     # Auth procedures
│       │           └── example.router.ts  # Example CRUD procedures
│       └── trpc/
│           ├── client.ts           # Browser tRPC client singleton (trpc())
│           └── index.ts            # Re-exports trpc() and AppRouter
└── src/routes/
    ├── +layout.svelte              # Root layout (CSS, favicon)
    ├── +layout.server.ts           # Exposes user/session to all pages
    ├── +page.svelte                # Landing page
    ├── layout.css                  # Tailwind + shadcn CSS variables + theme
    ├── (auth)/                     # Unauthenticated route group
    │   ├── +layout.server.ts       # Guard: redirect to /dashboard if logged in
    │   ├── +layout.svelte          # Centered card layout
    │   ├── login/                  # /login — email/password + GitHub OAuth
    │   ├── register/               # /register — sign up
    │   ├── forgot-password/        # /forgot-password — request reset email
    │   └── reset-password/         # /reset-password?token=… — set new password
    ├── (protected)/                # Authenticated route group
    │   ├── +layout.server.ts       # Guard: redirect to /login if not authenticated
    │   ├── +layout.svelte          # App shell with navbar + sign-out
    │   └── dashboard/              # /dashboard — tRPC demo (greeting + tasks CRUD)
    ├── api/trpc/[...trpc]/
    │   └── +server.ts              # tRPC HTTP endpoint (fetchRequestHandler)
    └── demo/better-auth/           # Legacy form-action auth demo (reference only)

4. Authentication

Auth is handled entirely by Better Auth. The server config lives in src/lib/server/auth.ts and supports email/password and GitHub OAuth out of the box.

How sessions work

hooks.server.ts calls auth.api.getSession() on every request and populates event.locals.user and event.locals.session. All server load functions and form actions can read these directly.

Route protection

Two SvelteKit route groups handle guards centrally — no per-page boilerplate needed:

  • (auth)/+layout.server.ts — redirects to /dashboard if the user is already logged in
  • (protected)/+layout.server.ts — redirects to /login if the user is not authenticated

Client-side auth

import { signIn, signUp, signOut, useSession } from "$lib/auth-client";

// Reactive session store
const session = useSession();
// $session.data?.user, $session.isPending

// Sign in
await signIn.email({ email, password, callbackURL: "/dashboard" });

// GitHub OAuth
await signIn.social({ provider: "github", callbackURL: "/dashboard" });

// Sign out
await signOut();

Regenerating the auth schema

Run this after adding Better Auth plugins:

bun run auth:schema

5. tRPC

Server

Procedures are defined in src/lib/server/trpc/routers/. Two procedure types are available:

import { publicProcedure, protectedProcedure, router } from "../router";

export const myRouter = router({
  // No auth required
  hello: publicProcedure
    .input(z.object({ name: z.string() }))
    .query(({ input }) => `Hello ${input.name}`),

  // Requires a valid session — throws UNAUTHORIZED otherwise
  secret: protectedProcedure
    .query(({ ctx }) => ctx.user),
});

Register new routers in src/lib/server/trpc/index.ts.

The HTTP endpoint at /api/trpc uses fetchRequestHandler from @trpc/server/adapters/fetch — not createTRPCHandle, which returns a SvelteKit Handle and belongs in hooks.server.ts, not a +server.ts file.

Client

import { trpc } from "$lib/trpc";

// In a Svelte component (browser)
const result = await trpc().example.hello.query({ name: "World" });

// In a +page.ts load function (SSR-compatible)
export const load = async (event) => {
  return { tasks: await trpc(event).example.getTasks.query() };
};

The client is a singleton in the browser. Pass the SvelteKit event object for SSR-compatible fetching in load functions.


6. UI Components

Components are from shadcn-svelte, scaffolded with:

bunx shadcn-svelte@next init --preset b3yzKzMcEc

Always import from the barrel file to get full variant support:

import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
import { Card, CardHeader, CardTitle, CardContent, CardFooter, CardDescription } from "$lib/components/ui/card/index.js";

Add more components:

bunx shadcn-svelte@next add <component-name>

The theme is defined in src/routes/layout.css using OKLCH CSS variables and supports dark mode via the .dark class.


7. Database

App tables go in src/lib/server/db/schema.ts. Auth tables are auto-generated in auth.schema.ts and re-exported from schema.ts so there's a single import point.

bun run db:generate <name>   # generate migration from schema diff
bun run db:migrate           # apply pending migrations
bun run db:push              # push schema directly (dev shortcut)
bun run db:studio            # open Drizzle Studio

8. Scripts

Script Description
bun run dev Start dev server
bun run build Production build
bun run check Type check (svelte-check)
bun run db:start Start local PostgreSQL via Docker
bun run db:generate <name> Generate a new migration
bun run db:migrate Apply pending migrations
bun run db:push Push schema directly (dev only)
bun run db:studio Open Drizzle Studio
bun run auth:schema Regenerate Better Auth schema file

9. Key Design Decisions

Decision Rationale
better-auth/minimal import Smaller bundle — only includes what's used
fetchRequestHandler for tRPC endpoint createTRPCHandle returns a Handle for hooks, not a RequestHandler for +server.ts
Route groups (auth) / (protected) Centralises guards in layout files — no per-page boilerplate
Named exports from auth-client.ts Tree-shakeable — avoids importing the full client object everywhere
superjson configured on server only tRPC v11 transformer is set once on initTRPC; trpc-sveltekit client inherits it automatically
auth.schema.ts separate from schema.ts Keeps auto-generated code isolated; re-exported for a single import point
Biome over ESLint/Prettier Single tool, faster, zero config conflicts

Top categories

Loading Svelte Themes