惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
月光博客
月光博客
B
Blog RSS Feed
C
Check Point Blog
WordPress大学
WordPress大学
T
Tailwind CSS Blog
GbyAI
GbyAI
H
Help Net Security
Y
Y Combinator Blog
I
InfoQ
雷峰网
雷峰网
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
美团技术团队
博客园 - 三生石上(FineUI控件)
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
A
About on SuperTechFans
G
Google Developers Blog
爱范儿
爱范儿
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
U
Unit 42
人人都是产品经理
人人都是产品经理

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to Build a SvelteKit SPA with FastAPI Backend
Tortoise · 2026-04-26 · via DEV Community

Tortoise

Originally published at turtledev.io

In my previous post, I talked about why I moved from SvelteKit SSR to a Svelte SPA + FastAPI architecture. Today, I want to show you my setup with a simple project.

We'll build a simple todo list app to demonstrate how the frontend and backend communicate, and how to write less and type-safe code by using Orval to auto-generate TypeScript API clients from FastAPI's OpenAPI specs.

Building a production app? Check out FastSvelte - a production-ready FastAPI + SvelteKit starter with authentication, payments, and more built-in.

Project Structure

todo-app/
├── backend/          # FastAPI Python backend
│   ├── main.py       # FastAPI app
│   ├── models.py     # Pydantic models
│   └── requirements.txt
│
└── frontend/         # SvelteKit SPA
    ├── src/
    │   ├── routes/   # Pages
    │   └── lib/      # API client & components
    └── package.json

Enter fullscreen mode Exit fullscreen mode

Backend: FastAPI Setup

Create a backend directory and set up a virtual environment:

cd backend
python3 -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

Enter fullscreen mode Exit fullscreen mode

requirements.in

We'll use pip-compile from pip-tools to manage dependencies:

  1. Simple dependency specification: List only your direct dependencies without worrying about version pins
  2. Clear dependency tree: The generated requirements.txt shows direct vs transitive dependencies
  3. Reproducible builds: All versions are pinned for consistent installations
  4. Easy updates: Run pip-compile again to update to latest compatible versions
fastapi
uvicorn[standard]
pydantic

Enter fullscreen mode Exit fullscreen mode

Install pip-tools and compile dependencies

pip install pip-tools
pip-compile requirements.in
pip install -r requirements.txt

Enter fullscreen mode Exit fullscreen mode

models.py

# backend/models.py
from pydantic import BaseModel

class TodoCreate(BaseModel):
    title: str
    completed: bool = False

class TodoUpdate(BaseModel):
    title: str | None = None
    completed: bool | None = None

class Todo(BaseModel):
    id: int
    title: str
    completed: bool

Enter fullscreen mode Exit fullscreen mode

main.py

# backend/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from models import Todo, TodoCreate, TodoUpdate

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # Vite dev server
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

todos: dict[int, Todo] = {}
next_id = 1

@app.get("/todos", response_model=list[Todo], operation_id="listTodos")
def list_todos():
    """Get all todos"""
    return list(todos.values())

@app.post("/todos", response_model=Todo, operation_id="createTodo")
def create_todo(todo_data: TodoCreate):
    """Create a new todo"""
    global next_id
    todo = Todo(id=next_id, title=todo_data.title, completed=todo_data.completed)
    todos[next_id] = todo
    next_id += 1
    return todo

@app.get("/todos/{todo_id}", response_model=Todo, operation_id="getTodo")
def get_todo(todo_id: int):
    """Get a specific todo"""
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todos[todo_id]

@app.put("/todos/{todo_id}", response_model=Todo, operation_id="updateTodo")
def update_todo(todo_id: int, todo_data: TodoUpdate):
    """Update a todo"""
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    todo = todos[todo_id]
    if todo_data.title is not None:
        todo.title = todo_data.title
    if todo_data.completed is not None:
        todo.completed = todo_data.completed
    return todo

@app.delete("/todos/{todo_id}", status_code=204, operation_id="deleteTodo")
def delete_todo(todo_id: int):
    """Delete a todo"""
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    del todos[todo_id]

Enter fullscreen mode Exit fullscreen mode

Notice the operation_id on each route. This tells FastAPI to use clean names like listTodos in the OpenAPI spec instead of auto-generated ones like list_todos_todos_get. Orval will use these as TypeScript function names.

Start the backend

uvicorn main:app --reload

Enter fullscreen mode Exit fullscreen mode

API running at http://localhost:8000. Docs at http://localhost:8000/docs.

Test the API

# Create a todo
curl -X POST http://localhost:8000/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn FastAPI", "completed": false}'

# List all todos
curl http://localhost:8000/todos

# Update a todo
curl -X PUT http://localhost:8000/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"completed": true}'

# Delete a todo
curl -X DELETE http://localhost:8000/todos/1

Enter fullscreen mode Exit fullscreen mode

The OpenAPI spec is at http://localhost:8000/openapi.json — this is what Orval will use to generate the TypeScript client.

Frontend: SvelteKit SPA

npx sv create frontend

Enter fullscreen mode Exit fullscreen mode

Select: SvelteKit minimal, TypeScript, prettier, npm.

Configure as SPA

Create src/routes/+layout.ts:

export const csr = true;        // Enable client-side rendering
export const ssr = false;       // Disable server-side rendering
export const prerender = false; // Disable prerendering

Enter fullscreen mode Exit fullscreen mode

Install Dependencies

npm install axios
npm install -D orval

Enter fullscreen mode Exit fullscreen mode

Setup Auto-Generated API Client

Create orval.config.cjs:

module.exports = {
    default: {
        input: {
            target: 'http://localhost:8000/openapi.json'
        },
        output: {
            target: './src/lib/api/gen',
            schemas: './src/lib/api/gen/model',
            client: 'axios',
            mode: 'split',
            clean: true,
            baseUrl: 'http://localhost:8000'
        }
    }
};

Enter fullscreen mode Exit fullscreen mode

Add a generate script to package.json:

{
    "scripts": {
        "generate": "npx orval --config orval.config.cjs"
    }
}

Enter fullscreen mode Exit fullscreen mode

Generate TypeScript Client

With your backend running:

npm run generate

Enter fullscreen mode Exit fullscreen mode

This creates src/lib/api/gen/ with fully typed functions for all your endpoints.

Build the UI

Create src/routes/+page.svelte:

<script lang="ts">
    import { onMount } from 'svelte';
    import { getFastAPI } from '$lib/api/gen/fastAPI';
    import type { Todo } from '$lib/api/gen/model';

    const api = getFastAPI();

    let todos = $state<Todo[]>([]);
    let newTodoTitle = $state('');
    let loading = $state(false);

    async function loadTodos() {
        loading = true;
        try {
            const response = await api.listTodos();
            todos = response.data;
        } catch (error) {
            console.error('Failed to load todos:', error);
        } finally {
            loading = false;
        }
    }

    async function addTodo() {
        if (!newTodoTitle.trim()) return;
        try {
            const response = await api.createTodo({ title: newTodoTitle, completed: false });
            todos = [...todos, response.data];
            newTodoTitle = '';
        } catch (error) {
            console.error('Failed to create todo:', error);
        }
    }

    async function toggleTodo(todo: Todo) {
        try {
            const response = await api.updateTodo(todo.id, { completed: !todo.completed });
            todos = todos.map((t) => t.id === todo.id ? response.data : t);
        } catch (error) {
            console.error('Failed to update todo:', error);
        }
    }

    async function removeTodo(id: number) {
        try {
            await api.deleteTodo(id);
            todos = todos.filter((t) => t.id !== id);
        } catch (error) {
            console.error('Failed to delete todo:', error);
        }
    }

    onMount(() => { loadTodos(); });
</script>

<div class="container">
    <h1>Todo List</h1>

    <div class="add-todo">
        <input
            type="text"
            bind:value={newTodoTitle}
            placeholder="What needs to be done?"
            onkeydown={(e) => e.key === 'Enter' && addTodo()}
        />
        <button onclick={addTodo}>Add</button>
    </div>

    {#if loading}
        <p>Loading...</p>
    {:else if todos.length === 0}
        <p class="empty">No todos yet. Add one above!</p>
    {:else}
        <ul class="todo-list">
            {#each todos as todo (todo.id)}
                <li class:completed={todo.completed}>
                    <input type="checkbox" checked={todo.completed} onchange={() => toggleTodo(todo)} />
                    <span>{todo.title}</span>
                    <button class="delete" onclick={() => removeTodo(todo.id)}>×</button>
                </li>
            {/each}
        </ul>
    {/if}
</div>

Enter fullscreen mode Exit fullscreen mode

Start the frontend

npm run dev

Enter fullscreen mode Exit fullscreen mode

App running at http://localhost:5173.

Updating the API

When you add a field to a model:

class TodoCreate(BaseModel):
    title: str
    completed: bool = False
    priority: str = "medium"  # New field!

Enter fullscreen mode Exit fullscreen mode

Just regenerate:

npm run generate

Enter fullscreen mode Exit fullscreen mode

TypeScript will immediately show errors wherever you need to update the frontend. No manual type syncing.

What We Built

  • FastAPI backend with CRUD endpoints
  • SvelteKit SPA frontend
  • Auto-generated TypeScript API client from OpenAPI spec
  • Fully functional todo app with end-to-end type safety

Source code: GitHub

Next: How to Add Authentication to a SvelteKit SPA

See also: Full-stack FastAPI Tutorial 1: Project Setup & Tooling

If you want a production-ready version with authentication, multi-tenancy, and Stripe already wired up, check out FastSvelte.

Smooth coding!