---
title: "Convex: Deine reaktive Datenbank"
description: "Daten in Convex mit durchgängiger Typsicherheit und Live-Updates modellieren und abfragen und Indexes und Soft Deletes nutzen, wie es echte Produkte tun"
type: "lesson"
locale: "de-CH"
course: "Der moderne App-Stack - Auth, Daten und Payments"
number: "3.3"
canonical: "https://agenticschool.dev/de/kurse/modern-app-stack/convex-your-reactive-database"
datePublished: "2026-06-12"
dateModified: "2026-06-12"
---

# Convex: Deine reaktive Datenbank

- Kurs: Der moderne App-Stack - Auth, Daten und Payments
- Lektion: 3.3
- Dauer: 30 min
- Level: fortgeschritten
- Status: published
- Kanonische URL: https://agenticschool.dev/de/kurse/modern-app-stack/convex-your-reactive-database
- Sprache: de-CH

> Daten in Convex mit durchgängiger Typsicherheit und Live-Updates modellieren und abfragen und Indexes und Soft Deletes nutzen, wie es echte Produkte tun

## Zusammenfassung

Convex ist ein reaktives Backend: du definierst ein Schema, schreibst Queries und Mutations als TypeScript-Funktionen, und deine UI aktualisiert sich live, wann immer sich Daten ändern. Diese Lektion erklärt, was eine Datenbank wirklich ist versus eine Excel-Tabelle, warum Reaktivität zählt, das Schema-Queries-Mutations-Actions-Modell, die durchgängige Typsicherheit, die ganze Bug-Klassen tötet, wann du einen Index hinzufügst und das Soft-Delete-Muster, das dich gelöschte Daten wiederherstellen lässt, statt sie für immer zu verlieren.

## Was du lernst

- Was eine Datenbank versus eine Tabelle ist und warum eine reaktive Datenbank ändert, wie du baust
- Schema, Queries, Mutations und Actions, mit durchgängiger Typsicherheit von der Datenbank bis zur UI
- Indexes für schnelle Reads und Soft Delete versus Hard Delete mit einem echten Wiederherstellungsmuster

## Überblick

Deine Nutzer können sich einloggen. Jetzt brauchen sie Daten, die bestehen bleiben: ihr Profil, ihre Projekte, ihre gespeicherte Arbeit. Das ist die Datenbank-Schicht, und Convex passt besonders gut zu den Buildern, für die dieser Kurs gedacht ist, weil es etwas tut, das die meisten Datenbanken nicht tun - es ist reaktiv. Wenn sich Daten ändern, aktualisiert sich jeder Teil deiner UI, der von diesen Daten abhängt, von selbst, ohne manuellen Refresh-Code. Obendrein fliessen deine Typen durchgängig, von der Datenbank in deine React-Komponenten, sodass eine ganze Kategorie von Bugs schlicht nicht passieren kann. Diese Lektion lehrt das Modell: was eine Datenbank ist, die vier Arten von Funktion, die du schreibst, Indexes und die Delete-Muster, die ein Spielzeug von einem echten Produkt trennen.

## Was du lernst

Du lernst, was eine Datenbank tatsächlich ist und warum sie für eine App eine Tabelle schlägt, was dir Reaktivität bringt, wie du ein Schema definierst und Queries, Mutations und Actions schreibst, wie durchgängige Typsicherheit Bugs verhindert, bevor sie passieren, wann und warum du einen Index hinzufügst und den Unterschied zwischen Hard und Soft Deletes mit einem Wiederherstellungsmuster, das du kopieren kannst.

## Voraussetzungen

Eine funktionierende App mit Clerk-Auth aus der vorigen Lektion, denn du willst Daten an eingeloggte Nutzer binden. Die Architektur-Lektion, damit du dich erinnerst, dass die Datenbank die dritte Schicht ist. Und die Grundlagen-Seite dazu, was eine Datenbank ist, falls die Idee eines Schemas oder einer Tabelle brandneu ist. Vertrautheit mit einfachem TypeScript hilft, denn in Convex ist deine Datenbank-Logik einfach TypeScript-Funktionen.

## Das Problem

Einsteiger greifen zu dem, was sie kennen: einer Tabelle, einer CSV-Datei oder einem Haufen JSON, der auf die Festplatte gespeichert wird. Das funktioniert, bis zwei Nutzer dieselben Daten anfassen, oder du einen Datensatz unter zehntausend schnell finden musst, oder du die Form deiner Daten änderst und alles still bricht. Dann schrauben sie eine traditionelle Datenbank an die Seite und verbringen Tage mit Glue-Code: beim Laden holen, nach jeder Änderung neu holen, Ladezustände behandeln, die UI synchron halten und Stale-Data-Bugs jagen, bei denen der Bildschirm alte Zahlen zeigt. Das meiste dieses Glues ist reine Verschwendung. Eine reaktive, typisierte Datenbank löscht den Glue und die Bug-Klasse gleich mit.

## Eine Datenbank ist keine Tabelle

Eine Excel-Tabelle oder eine CSV ist in Ordnung für eine Liste, die du mit dem Auge liest. Eine Datenbank ist für einen anderen Job gebaut: viele Nutzer, die gleichzeitig lesen und schreiben, bestimmte Datensätze schnell finden, selbst unter Millionen, die Form deiner Daten erzwingen, sodass keine schlechte Zeile sich einschleichen kann, und nie Daten verlieren oder korrumpieren, wenn zwei Dinge gleichzeitig passieren. Eine Tabelle hat keine Ahnung, wer sie sonst noch bearbeitet, und keine Möglichkeit zu garantieren, dass eine "Preis"-Spalte immer eine Zahl ist. Eine Datenbank kann beides. Das mentale Upgrade ist dieses: eine Tabelle ist ein Dokument, das du ansiehst; eine Datenbank ist ein Dienst, mit dem deine App redet und der garantiert, dass die Daten korrekt und konsistent bleiben, egal wie viele Nutzer oder wie viel Last. In dem Moment, in dem deine Daten zwischen Nutzern geteilt werden oder schnell abgefragt werden müssen, bist du der Tabelle entwachsen.

- Tabelle/CSV: ein Editor zur Zeit, keine Garantien zur Datenform, langsam zu durchsuchen im grossen Massstab, leicht zu korrumpieren.
- Datenbank: viele gleichzeitige Nutzer, erzwungene Datenform (das Schema), schnelle Lookups via Indexes, sicher unter Last.
- Convex fügt Reaktivität obendrauf: Änderungen werden automatisch an jeden verbundenen Client gepusht.

## Warum Reaktivität zählt

In einem normalen Setup schreibst du viel Code, um den Bildschirm mit der Datenbank synchron zu halten: holen, wenn die Seite lädt, neu holen, nachdem du etwas geändert hast, nach Updates von anderen Nutzern pollen, Lade- und Fehlerzustände von Hand managen. Jeder davon ist eine Chance für einen Bug, bei dem die UI veraltete Daten zeigt. Convex dreht das um. Eine Query ist ein Live-Abonnement: du schreibst sie einmal, und wann immer sich Daten ändern, die sie liest - ob du sie geändert hast oder ein anderer Nutzer -, pusht Convex das neue Ergebnis an deine Komponente und sie rendert neu. Du löschst die Refetch-Logik, das Polling und die ganze Stale-Data-Bug-Klasse. Für ein kollaboratives oder Echtzeit-Produkt (ein Dashboard, ein Chat, eine geteilte Liste) ist das transformativ, und selbst für eine einfache App entfernt es einen Haufen mühsamer, fehleranfälliger Verkabelung.

## Schema, Queries, Mutations und Actions

Convex gibt dir vier Bausteine. Das Schema deklariert die Form deiner Daten - deine Tabellen und ihre Felder und Typen - sodass die Datenbank sie erzwingen kann. Queries lesen Daten und sind reaktiv. Mutations ändern Daten (insert, update, delete) und laufen in einer Transaktion, sodass sie entweder voll gelingen oder voll scheitern. Actions sind dafür, mit der Aussenwelt zu reden (eine externe API aufrufen, eine E-Mail senden), wo du etwas tun musst, das kein reines Datenbank-Lesen oder -Schreiben ist. Hier ist ein echtes Schema plus eine Query und eine Mutation, die alltäglichen Bewegungen, die du ständig schreiben wirst.

```typescript
// convex/schema.ts - die Form deiner Daten, von der Datenbank erzwungen
import { defineSchema, defineTable } from 'convex/server'
import { v } from 'convex/values'

export default defineSchema({
  projects: defineTable({
    ownerId: v.string(), // die Clerk-User-ID, der dieses Projekt gehört
    name: v.string(),
    archivedAt: v.optional(v.number()), // null/abwesend = aktiv; ein Timestamp = soft-deleted
  })
    // Index, damit 'finde die Projekte dieses Nutzers' ein schneller Lookup ist, kein Full Scan
    .index('by_owner', ['ownerId']),
})
```
Ein Schema mit einer typisierten Tabelle und einem Index. archivedAt ist das Feld, das Soft Delete antreibt.

```typescript
// convex/projects.ts - eine Query (Read, reaktiv) und eine Mutation (Write, transaktional)
import { query, mutation } from './_generated/server'
import { v } from 'convex/values'

// Reaktiv: jeder Client, der das abonniert hat, rendert neu, wenn sich die Daten ändern.
export const listActive = query({
  args: { ownerId: v.string() },
  handler: async (ctx, { ownerId }) => {
    const rows = await ctx.db
      .query('projects')
      .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
      .collect()
    // Soft-deleted Zeilen haben archivedAt gesetzt - verbirg sie aus der aktiven Liste.
    return rows.filter((row) => row.archivedAt === undefined)
  },
})

export const create = mutation({
  args: { ownerId: v.string(), name: v.string() },
  handler: async (ctx, { ownerId, name }) => {
    return await ctx.db.insert('projects', { ownerId, name })
  },
})
```
Queries lesen und sind live; Mutations schreiben in einer Transaktion. Beide sind schlichtes TypeScript.

## Durchgängige Typsicherheit

Das ist die stille Superkraft. Weil dein Schema TypeScript ist und Convex Typen daraus generiert, fliesst der Typ deiner Daten den ganzen Weg von der Datenbank in deine React-Komponenten. Wenn du ein Feld im Schema umbenennst, wird jede Query, Mutation und Komponente, die den alten Namen genutzt hat, in deinem Editor rot, bevor du die App je ausführst. Du kannst nicht versehentlich ein Feld lesen, das nicht existiert, einer Mutation das falsche Argument übergeben oder annehmen, dass ein String eine Zahl ist. Ganze Klassen von "es ist in Produktion abgestürzt, weil die Daten nicht die Form hatten, die ich annahm"-Bugs werden unmöglich, weil dein Editor und der Typechecker sie fangen, während du tippst. Für jemanden, der mit einem Agent baut, ist das riesig: der Agent bekommt dieselben Typsignale und schreibt weit öfter korrekten Code, und dein Typecheck-Quality-Gate fängt den Rest, bevor er shippt.

## Indexes: Reads schnell machen

Wenn du die Datenbank fragst "gib mir alle Projekte, die diesem Nutzer gehören", hat sie zwei Wege zu antworten. Ohne Index scannt sie jede Zeile und prüft jede - in Ordnung für zehn Zeilen, schmerzhaft langsam für eine Million. Mit einem Index auf ownerId springt sie direkt zu den passenden Zeilen, wie das Register am Ende eines Buchs zu nutzen, statt jede Seite zu lesen. Die Regel ist einfach: jedes Feld, nach dem du regelmässig filterst oder sortierst, verdient einen Index. Du hast es im Schema oben gesehen - der by_owner-Index ist, was listActive schnell macht, egal wie viele Projekte insgesamt existieren. Indexes früh hinzuzufügen kostet fast nichts; zu entdecken, dass du sie brauchtest, wenn deine App unter echten Daten zum Kriechen kommt, kostet eine stressige Debugging-Session.

- Kein Index: die Datenbank prüft jede Zeile (ein Full Scan). In Ordnung für winzige Tabellen, langsam im grossen Massstab.
- Mit einem Index: sie springt direkt zu den passenden Zeilen. Schnell selbst mit Millionen Zeilen.
- Indexiere die Felder, nach denen du oft filterst oder sortierst - ownerId, status, createdAt sind häufig.
- Definiere den Index im Schema; nutze ihn in Queries mit .withIndex(...).

## Soft Delete versus Hard Delete, mit Wiederherstellung

Ein Hard Delete entfernt eine Zeile dauerhaft - sie ist weg, und jede Chance, sie zurückzubekommen, auch. Ein Soft Delete behält die Zeile, markiert sie aber als gelöscht, meist mit einem Timestamp wie dem archivedAt-Feld im Schema oben. Echte Produkte bevorzugen für nutzerseitige Daten fast immer Soft Deletes, denn Nutzer löschen ständig Dinge aus Versehen, und "tut mir leid, das ist für immer weg" ist eine schreckliche Erfahrung und manchmal ein Compliance-Problem. Mit einem Soft Delete kannst du einen Papierkorb oder ein Undo anbieten, die Daten wiederherstellen und Historie und Audit-Trails intakt halten. Du machst erst später einen Hard Delete, nach Zeitplan, für Daten, die lange genug soft-deleted waren und die du rechtlich löschen darfst. Hier ist das Muster: Löschen setzt den Timestamp, Wiederherstellen leert ihn, und ein Hintergrund-Job kann wirklich alte Zeilen hart löschen.

```typescript
// Soft Delete: markiere es, zerstöre es nicht. Wiederherstellen ist dann trivial.
export const softDelete = mutation({
  args: { id: v.id('projects') },
  handler: async (ctx, { id }) => {
    await ctx.db.patch(id, { archivedAt: Date.now() })
  },
})

export const restore = mutation({
  args: { id: v.id('projects') },
  handler: async (ctx, { id }) => {
    await ctx.db.patch(id, { archivedAt: undefined })
  },
})

// Hard Delete: nur für lang soft-deleted Daten, meist von einem geplanten Job ausgeführt.
export const purge = mutation({
  args: { id: v.id('projects') },
  handler: async (ctx, { id }) => {
    await ctx.db.delete(id)
  },
})
```
Soft Delete setzt ein Flag und bleibt wiederherstellbar; Hard Delete ist endgültig und für alte, löschbare Daten reserviert.

## Typische Fehler

Die häufigen: manuellen Refetch- und Polling-Code schreiben, wenn Convex-Queries schon reaktiv sind, sodass du gegen das Tool kämpfst; Indexes vergessen und zusehen, wie die App kriecht, sobald echte Daten ankommen; Nutzerdaten ohne Wiederherstellung hart löschen und dann das Support-Ticket bekommen, das du nicht beheben kannst; versuchen, eine externe API in einer Query oder Mutation aufzurufen statt in einer Action; und dein Schema lockern (alles optional, alles ein String), bis die Typsicherheit, die dich schützte, verdunstet. Lehn dich ins Schema und die Reaktivität, statt um sie herum zu arbeiten.

## Business-ROI

Eine reaktive, typisierte Datenbank ist Produktivitäts-Multiplikator und Qualitäts-Multiplikator zugleich. Du schreibst dramatisch weniger Verkabelung, also shippen Features schneller. Durchgängige Typen fangen Bugs, bevor sie einen Kunden erreichen, also shippst du mit weniger Bränden. Indexes halten die App schnell, während du wächst, was sowohl Conversion als auch deinen Ruf schützt. Und das Soft-Delete-Muster verwandelt eine Kategorie katastrophaler Support-Vorfälle - "ich habe aus Versehen alles gelöscht" - in ein Ein-Klick-Restore. Für eine Gründerin sind die bei der Verkabelung gesparte Zeit und die Bugs, die nie passieren, weit mehr wert als die moderaten Kosten des Dienstes.

## Checkliste

Du bist bereit weiterzugehen, wenn jedes davon in einer echten App stimmt, die du gebaut hast, nicht nur in der Theorie verstanden.

- Du kannst erklären, warum eine Datenbank eine Tabelle schlägt, sobald Daten geteilt oder gross sind.
- Du hast ein Schema mit mindestens einer Tabelle, einem Index und funktionierenden Query- und Mutation-Funktionen.
- Du kannst Typen vom Schema in deine Komponenten fliessen sehen und einen Rename-Fehler im Editor fangen.
- Du hast Soft Delete mit einem funktionierenden Restore implementiert und weisst, wann ein Hard Delete angebracht ist.

## Ressourcen

Die Convex-Docs sind ausgezeichnet und aktuell - halt die Seiten zu Schema, Queries, Mutations und Indexes als Lesezeichen. Die Grundlagen-Seite dazu, was eine Datenbank ist, erdet die Konzepte, falls eines wackelig war. Du hast jetzt Auth und Daten; die nächste Lektion sichert die Secrets ab, die jeden Dienst verbinden, den du hinzugefügt hast, inklusive wie du API-Keys verschlüsselst, die deine Nutzer dir anvertrauen.

## Deine Aufgabe

Füge eine Convex-Tabelle für etwas hinzu, das deine App braucht (eine Liste von Items, Projekten oder Notizen, an den eingeloggten Nutzer gebunden), mit einem Index auf den Owner. Schreib eine reaktive Query und eine Create-Mutation, implementiere dann Soft Delete und Restore. Lösche ein Item, bestätige, dass es aus der Liste verschwindet, stell es dann wieder her und sieh zu, wie es ohne Seiten-Refresh wieder erscheint. Diese eine Übung beweist, dass du Reaktivität, Indexes und wiederherstellbare Deletes alle auf einmal verstehst.

## Nächste Lektion

Deine App hat jetzt Nutzer und Daten, was heisst, sie hat einen wachsenden Haufen geheimer Keys, die sie verbinden. Die nächste Lektion ist die disziplinierte Version des Secret-Handlings: env-Dateien, .gitignore, was zu tun ist, wenn du je ein Secret gepusht hast, Convex-Deploy-Keys und das Verschlüsseln von Nutzer-API-Keys statt sie als Klartext zu speichern.

## Transkript

Convex ist ein reaktives Backend: du definierst ein Schema, schreibst Queries und Mutations als TypeScript-Funktionen, und deine UI aktualisiert sich live, wann immer sich Daten ändern. Diese Lektion erklärt, was eine Datenbank wirklich ist versus eine Excel-Tabelle, warum Reaktivität zählt, das Schema-Queries-Mutations-Actions-Modell, die durchgängige Typsicherheit, die ganze Bug-Klassen tötet, wann du einen Index hinzufügst und das Soft-Delete-Muster, das dich gelöschte Daten wiederherstellen lässt, statt sie für immer zu verlieren.
