SvelteKit

SvelteKit integration with Litestar Vite for full-stack Svelte applications.

Quick Start

litestar assets init --template sveltekit

This creates a SvelteKit project with TypeScript support and SSR capabilities.

Project Structure

SvelteKit applications use a specialized Vite plugin:

my-app/
├── app.py              # Litestar backend
├── package.json
├── vite.config.ts      # Vite + SvelteKit plugin
├── svelte.config.js    # SvelteKit configuration
├── tsconfig.json
├── src/
│   ├── app.html        # HTML template
│   ├── routes/
│   │   └── +page.svelte    # Pages (file-based routing)
│   └── lib/
│       └── api/        # Generated types from OpenAPI
└── build/              # SvelteKit build output

Backend Setup

examples/sveltekit/app.py
"""SvelteKit example - shared "Library" backend for SvelteKit SSR frontend.

SvelteKit runs as an SSR server. In dev mode, Litestar proxies all non-API
routes to the SvelteKit dev server. In production, SvelteKit's `hooks.server.ts`
proxies /api/* requests to the Litestar backend.

All examples in this repository expose the same backend:
- `/api/summary` - overview + featured book
- `/api/books` - list of books
- `/api/books/{book_id}` - single book

Dev mode (default):
    litestar --app-dir examples/sveltekit run

Production (two terminals):
    # Terminal 1: Install deps, build, and serve SvelteKit SSR server
    litestar --app-dir examples/sveltekit assets install
    litestar --app-dir examples/sveltekit assets build
    litestar --app-dir examples/sveltekit assets serve

    # Terminal 2: Litestar API server
    VITE_DEV_MODE=false litestar --app-dir examples/sveltekit run

The SvelteKit server includes `hooks.server.ts` which proxies /api/* requests
to the Litestar backend (configurable via LITESTAR_API env var, default: localhost:8000).
"""

import os
from pathlib import Path

from litestar import Controller, Litestar, get
from litestar.exceptions import NotFoundException
from msgspec import Struct

from litestar_vite import PathConfig, RuntimeConfig, TypeGenConfig, ViteConfig, VitePlugin

here = Path(__file__).parent
DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in {"true", "1", "yes"}


class Book(Struct):
    id: int
    title: str
    author: str
    year: int
    tags: list[str]


class Summary(Struct):
    app: str
    headline: str
    total_books: int
    featured: Book


BOOKS: list[Book] = [
    Book(id=1, title="Async Python", author="C. Developer", year=2024, tags=["python", "async"]),
    Book(id=2, title="Type-Safe Web", author="J. Dev", year=2025, tags=["typescript", "api"]),
    Book(id=3, title="Frontend Patterns", author="A. Designer", year=2023, tags=["frontend", "ux"]),
]


def _get_book(book_id: int) -> Book:
    for book in BOOKS:
        if book.id == book_id:
            return book
    raise NotFoundException(detail=f"Book {book_id} not found")


def _get_summary() -> Summary:
    """Build summary data."""
    return Summary(
        app="litestar-vite library", headline="One backend, many frontends", total_books=len(BOOKS), featured=BOOKS[0]
    )


class LibraryController(Controller):
    """Library API controller."""

    @get("/api/summary")
    async def summary(self) -> Summary:
        """Overview endpoint used across all examples."""
        return _get_summary()

    @get("/api/books")
    async def books(self) -> list[Book]:
        """Return all books."""
        return BOOKS

    @get("/api/books/{book_id:int}")
    async def book_detail(self, book_id: int) -> Book:
        """Return a single book by id."""
        return _get_book(book_id)


vite = VitePlugin(
    config=ViteConfig(
        mode="ssr",  # SSR mode: proxy in dev, Node serves HTML in prod
        dev_mode=DEV_MODE,
        paths=PathConfig(root=here),
        types=TypeGenConfig(output=Path("src/lib/generated"), generate_zod=True),
        # Fixed port for E2E tests - can be removed for local dev or customized for production
        runtime=RuntimeConfig(port=5022),
    )
)

app = Litestar(route_handlers=[LibraryController], plugins=[vite], debug=True)

Key points:

  • mode="framework" enables meta-framework integration mode (aliases: mode="ssr" / mode="ssg")

  • ExternalDevServer delegates dev server to SvelteKit

  • TypeGenConfig enables type generation for SvelteKit

Vite Configuration

vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite"
import tailwindcss from "@tailwindcss/vite"
import { litestarSvelteKit } from "litestar-vite-plugin/sveltekit"
import { defineConfig } from "vite"

// Litestar manages the dev server port via VITE_PORT and runtime config.
// The SvelteKit plugin reads the port automatically - no hardcoding needed.
// LITESTAR_PORT is the backend API server port (default 8000).
const LITESTAR_PORT = process.env.LITESTAR_PORT ?? "8000"

export default defineConfig({
  plugins: [
    tailwindcss(),
    litestarSvelteKit({
      apiProxy: `http://localhost:${LITESTAR_PORT}`,
      apiPrefix: "/api",
    }),
    sveltekit(),
  ],
})

The litestarSvelteKit plugin must come before the sveltekit() plugin.

Configuration options:

  • apiProxy - URL of Litestar backend (default: http://localhost:8000)

  • apiPrefix - API route prefix to proxy (default: /api)

  • types - Enable type generation (reads from .litestar.json or explicit config)

API Integration

Using Load Functions

SvelteKit’s load functions are perfect for SSR data fetching:

// src/routes/users/+page.ts
import type { PageLoad } from './$types'
import type { User } from '$lib/api/types.gen'
import { route } from '$lib/api/routes'

export const load: PageLoad = async ({ fetch }) => {
  const response = await fetch(route('users:list'))
  const users: User[] = await response.json()
  return { users }
}
<!-- src/routes/users/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types'

  export let data: PageData
</script>

<h1>Users</h1>
<ul>
  {#each data.users as user (user.id)}
    <li>{user.name}</li>
  {/each}
</ul>

Server-Side API Calls

Use +page.server.ts for server-only logic:

// src/routes/users/+page.server.ts
import type { PageServerLoad } from './$types'
import type { User } from '$lib/api/types.gen'

export const load: PageServerLoad = async ({ fetch }) => {
  const response = await fetch('http://localhost:8000/api/users')
  const users: User[] = await response.json()
  return { users }
}

Client-Side Fetch

<script lang="ts">
  import { onMount } from 'svelte'
  import type { Summary } from '$lib/api/types.gen'
  import { route } from '$lib/api/routes'

  let summary = $state<Summary | null>(null)

  onMount(async () => {
    const res = await fetch(route('summary'))
    summary = await res.json()
  })
</script>

{#if summary}
  <h1>{summary.headline}</h1>
{/if}

Running

# Recommended: Litestar manages both servers
litestar run --reload

# Alternative: Run separately
litestar assets serve --production  # SvelteKit server
litestar run --reload               # Backend API (in another terminal)

Type Generation

With types=TypeGenConfig() enabled in Python:

litestar assets generate-types

This generates TypeScript types in src/lib/api/ (configured via .litestar.json).

The types are accessible via SvelteKit’s $lib alias:

import type { User } from '$lib/api/types.gen'
import { route } from '$lib/api/routes'

Deployment

For production:

  1. Build SvelteKit:

    litestar assets build
    
  2. Serve both apps:

    litestar assets serve --production  # SvelteKit adapter server
    litestar run                        # Backend API
    

The build uses SvelteKit’s adapter (e.g., @sveltejs/adapter-node for Node.js deployment).

See Also