S SvelteKurs
M10 · Hooks, Auth & Praxis

CRUD-App: DevNotes

Alles zusammengesetzt: Laden, Anlegen, Bearbeiten und Löschen von Notizen — die echte App dieses Kurses.

Die App, die du gerade benutzt

DevNotes ist eine vollständige CRUD-App, gebaut aus genau den Bausteinen der letzten Module: Load-Funktionen zum Anzeigen, Form Actions zum Verändern, Hooks für die Anmeldung und Drizzle/Postgres als Speicher. Kein einziges manuelles fetch.

🛤️ Rails-Vergleich: der CRUD-Controller

Die Struktur ist dir vertraut: index lädt, create/destroy verändern. In SvelteKit ist load das index, und benannte Actions sind create/update/destroy.

Rails ruby
# Rails: der klassische CRUD-Controller
class NotesController < ApplicationController
  def index;  @notes = current_user.notes.order(updated_at: :desc); end
  def create; current_user.notes.create!(note_params); redirect_to notes_path; end
  def destroy; current_user.notes.find(params[:id]).destroy; end
end

1. Laden (Read)

Die load-Funktion holt die Notizen des angemeldeten Users:

src/routes/devnotes/+page.server.ts ts
import { desc, eq } from 'drizzle-orm';
import { db, schema } from '$lib/server/db';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals }) => {
  if (!locals.user) throw redirect(303, '/login');   // Guard (before_action)
  const notes = await db.select().from(schema.notes)
    .where(eq(schema.notes.userId, locals.user.id))
    .orderBy(desc(schema.notes.updatedAt));
  return { user: locals.user, notes };               // landet in data-Prop
};

2. Verändern (Create / Delete)

Benannte Form Actions in derselben Datei kümmern sich um Mutationen:

src/routes/devnotes/+page.server.ts ts
import { fail, redirect } from '@sveltejs/kit';
import { and, eq } from 'drizzle-orm';

export const actions = {
  create: async ({ request, locals }) => {
    if (!locals.user) throw redirect(303, '/login');
    const data = await request.formData();
    const title = String(data.get('title') ?? '').trim();
    if (!title) return fail(400, { error: 'Titel darf nicht leer sein.' });
    await db.insert(schema.notes).values({ userId: locals.user.id, title });
    return { success: true };
  },
  delete: async ({ request, locals }) => {
    const id = Number((await request.formData()).get('id'));
    await db.delete(schema.notes)
      .where(and(eq(schema.notes.id, id), eq(schema.notes.userId, locals.user!.id)));
    return { success: true };
  }
};

3. Die Oberfläche

Die Seite bekommt data (aus load) und form (aus der Action) als Props. use:enhance macht die Formulare zu SPA-Interaktionen — funktioniert aber auch ohne JavaScript:

src/routes/devnotes/+page.svelte svelte
<script lang="ts">
  import { enhance } from '$app/forms';
  let { data, form } = $props();   // data.notes aus load, form aus der Action
</script>

<form method="POST" action="?/create" use:enhance>
  <input name="title" placeholder="Neue Notiz…" required />
  <button>Anlegen</button>
</form>

{#each data.notes as note (note.id)}
  <li>
    <a href="/devnotes/{note.id}">{note.title}</a>
    <form method="POST" action="?/delete" use:enhance>
      <input type="hidden" name="id" value={note.id} />
      <button>Löschen</button>
    </form>
  </li>
{/each}

💡 Progressive Enhancement

Ohne use:enhance senden die Formulare einen echten POST und die Seite lädt neu — die App funktioniert also auch ganz ohne JS. Mit use:enhance wird derselbe Ablauf ohne Reload abgewickelt. Das Beste aus beiden Welten.

Bearbeiten unter /devnotes/[id]

Die Detail-/Bearbeiten-Route nutzt eine dynamische [id]-Route (Modul 8) mit eigener load (Notiz holen, Besitz prüfen) und einer update-Action.

Zusammenfassung

💡 Das Wichtigste

  • Eine CRUD-Ressource = load (Read) + benannte Actions (Create/Update/Delete).
  • Besitz-Prüfung gehört in die Query (WHERE user_id = …).
  • use:enhance = SPA-Gefühl mit voller No-JS-Fallback-Sicherheit.

🎯 Probier es selbst

Öffne DevNotes, melde dich an (oder registriere dich) und lege eine Notiz an. Schau dir dann im Code an, welche Action dabei ausgelöst wird.