Claude Code SQLite: Datenbankentwicklung für lokale Apps mit KI

SQLite ist die am häufigsten eingesetzte Datenbank der Welt — in Browsern, Mobilgeräten, Desktop-Apps, Embedded-Systemen und Testumgebungen. Trotzdem wirkt der Einstieg oft komplizierter als nötig: Welche Node.js-Bibliothek für welchen Use Case? Wie strukturiert man Migrations sauber? Wann lohnt sich WAL-Modus? Claude Code beantwortet diese Fragen nicht abstrakt, sondern direkt im Kontext deines Projekts.

Dieser Artikel zeigt, wie du SQLite-Datenbankentwicklung mit Claude Code beschleunigst — von der ersten Tabelle bis zu automatisch generierten Migrations und Performance-Tuning. Die Beispiele sind praxisnah: better-sqlite3, node:sqlite (Node.js 22+), Drizzle ORM und rohe SQL-Skripte.

Claude Code Mastery — Datenbankentwicklung, Agents, Hooks auf Deutsch

SQLite ist ein Kapitel. Im Kurs lernst du den vollständigen Workflow: Schema-Design, Migrations, Testing, Agents und produktive Claude Code-Workflows auf Deutsch. Einmalig bezahlt, kein Abo.

Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

1. Wann SQLite die richtige Wahl ist

SQLite ist keine "kleine" Datenbank — es ist eine andere Kategorie. Kein Server, keine Verbindungen, keine Konfiguration. Die Datenbank ist eine einzige Datei, die deine Anwendung direkt liest und schreibt. Das macht SQLite ideal für:

Für Multi-User-Webanwendungen mit hoher Schreiblast ist PostgreSQL oder MySQL die bessere Wahl. SQLite glänzt, wenn die Datenbank nah an der Anwendung lebt — auf demselben Rechner, im selben Prozess.

SQLite vs. PostgreSQL: Die Grenze liegt bei gleichzeitigen Schreibzugriffen. SQLite sperrt die gesamte Datei beim Schreiben. Mit WAL-Modus (Write-Ahead Logging) können mehrere Leser gleichzeitig lesen, während ein Schreiber aktiv ist — das löst die meisten Parallelitätsprobleme für typische Desktop-App-Szenarien.

2. Setup: better-sqlite3 vs. node:sqlite

In Node.js gibt es zwei seriöse Optionen für SQLite. Claude Code kennt beide und wählt die passende für deinen Context:

better-sqlite3 (etabliert, synchron)

npm install better-sqlite3
npm install -D @types/better-sqlite3
import Database from 'better-sqlite3';

const db = new Database('app.db');

db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');

const createUsers = db.prepare(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL,
    created_at INTEGER NOT NULL DEFAULT (unixepoch())
  )
`);

createUsers.run();

better-sqlite3 ist synchron — kein await, kein Callback-Hell. Für Electron-Apps und CLI-Tools ist das oft genau das Richtige: einfacher, linearer Code ohne asynchrone Komplexität.

node:sqlite (Node.js 22+, nativ)

import { DatabaseSync } from 'node:sqlite';

const db = new DatabaseSync('app.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL,
    created_at INTEGER NOT NULL DEFAULT (unixepoch())
  )
`);

Das native Modul braucht keine externe Dependency. Für neue Projekte auf Node.js 22+ ist es eine gute Wahl, wenn du Abhängigkeiten minimieren willst. Die API ist ähnlich zu better-sqlite3, aber noch nicht feature-vollständig.

3. Prepared Statements und Transaktionen

Rohe String-Interpolation in SQL-Queries ist eine der häufigsten Sicherheitslücken. Prepared Statements lösen das — und sind gleichzeitig schneller, weil die Query einmal kompiliert und mehrfach ausgeführt wird.

const insertUser = db.prepare(
  'INSERT INTO users (email, name) VALUES (?, ?)'
);

const getUserByEmail = db.prepare(
  'SELECT * FROM users WHERE email = ?'
);

const updateName = db.prepare(
  'UPDATE users SET name = ? WHERE id = ?'
);

insertUser.run('alice@example.com', 'Alice');
const user = getUserByEmail.get('alice@example.com');
updateName.run('Alice Smith', user.id);

Für mehrere zusammenhängende Schreiboperationen sind Transaktionen entscheidend — entweder alles schlägt fehl, oder alles gelingt:

const transferCredits = db.transaction((fromId, toId, amount) => {
  const debit = db.prepare(
    'UPDATE accounts SET credits = credits - ? WHERE id = ? AND credits >= ?'
  );
  const credit = db.prepare(
    'UPDATE accounts SET credits = credits + ? WHERE id = ?'
  );

  const result = debit.run(amount, fromId, amount);
  if (result.changes === 0) {
    throw new Error('Insufficient credits');
  }
  credit.run(amount, toId);
});

transferCredits(userId1, userId2, 100);

Claude Code schreibt Transaktionen wie diese auf Anfrage — beschreibe einfach die Geschäftslogik, und es generiert den korrekten Code inklusive Fehlerbehandlung.

4. WAL-Modus und Performance-Tuning

Der wichtigste Performance-Schalter für SQLite ist der WAL-Modus (Write-Ahead Logging). Er trennt Lese- und Schreiboperationen, sodass Leser nicht auf laufende Schreiber warten müssen.

db.pragma('journal_mode = WAL');

db.pragma('synchronous = NORMAL');

db.pragma('cache_size = -64000');

db.pragma('temp_store = MEMORY');

db.pragma('mmap_size = 268435456');

Diese Pragmas solltest du einmalig beim Öffnen der Datenbank setzen. Claude Code erklärt auf Anfrage, welche Werte für deinen Use Case sinnvoll sind — eine Electron-App mit häufigen kleinen Schreibvorgängen hat andere Anforderungen als ein CLI-Tool das selten, aber groß schreibt.

Indexe nicht vergessen: WAL-Modus hilft bei Parallelität, aber für Query-Performance sind Indexe entscheidend. Claude Code analysiert deine häufigen Queries und schlägt passende Indexe vor — frag einfach: "Welche Indexe brauche ich für diese Queries?"

5. In-Memory-Datenbank für Tests

Einer der größten Vorteile von SQLite im Test-Setup: die In-Memory-Datenbank. Kein Aufräumen zwischen Tests, keine Testdaten die sich anhäufen, kein Netzwerk. Jeder Test bekommt eine frische, leere Datenbank.

import Database from 'better-sqlite3';
import { describe, it, beforeEach, expect } from 'vitest';

function createTestDb() {
  const db = new Database(':memory:');
  db.pragma('foreign_keys = ON');

  db.exec(`
    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      email TEXT NOT NULL UNIQUE,
      name TEXT NOT NULL
    );
    CREATE TABLE posts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL REFERENCES users(id),
      title TEXT NOT NULL,
      body TEXT NOT NULL
    );
  `);

  return db;
}

describe('UserRepository', () => {
  let db: Database.Database;

  beforeEach(() => {
    db = createTestDb();
  });

  it('should create a user', () => {
    const insert = db.prepare('INSERT INTO users (email, name) VALUES (?, ?)');
    insert.run('test@example.com', 'Test User');

    const user = db.prepare('SELECT * FROM users WHERE email = ?')
      .get('test@example.com') as { id: number; email: string; name: string };

    expect(user.email).toBe('test@example.com');
    expect(user.name).toBe('Test User');
  });
});

Mit ':memory:' als Datenbankpfad lebt die Datenbank nur im RAM. Tests laufen schnell — typisch unter 10 ms pro Test, auch wenn das Schema komplex ist.

6. Migrations mit Drizzle ORM oder raw SQL

Schemaänderungen ohne Migration-System enden im Chaos. SQLite hat keine ALTER COLUMN-Unterstützung — Spalten ändern bedeutet eine neue Tabelle erstellen, Daten kopieren, alte Tabelle löschen. Migrations halten diesen Prozess nachvollziehbar.

Drizzle ORM (empfohlen für TypeScript-Projekte)

npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  createdAt: integer('created_at', { mode: 'timestamp' })
    .$defaultFn(() => new Date()),
});

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  userId: integer('user_id').notNull().references(() => users.id),
  title: text('title').notNull(),
  body: text('body').notNull(),
});
npx drizzle-kit generate
npx drizzle-kit migrate

Claude Code kann das komplette Schema aus einer Beschreibung generieren: "Ich brauche eine Tabelle für Benutzer mit E-Mail, Name und Erstellungsdatum, verknüpft mit Posts die einen Titel und einen Body haben." Ergebnis: vollständiges Drizzle-Schema plus Migration.

Raw SQL Migrations (ohne ORM)

import Database from 'better-sqlite3';
import { readdirSync, readFileSync } from 'fs';
import { join } from 'path';

function runMigrations(db: Database.Database, migrationsDir: string) {
  db.exec(`
    CREATE TABLE IF NOT EXISTS migrations (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      filename TEXT NOT NULL UNIQUE,
      applied_at INTEGER NOT NULL DEFAULT (unixepoch())
    )
  `);

  const applied = new Set(
    (db.prepare('SELECT filename FROM migrations').all() as { filename: string }[])
      .map(r => r.filename)
  );

  const files = readdirSync(migrationsDir)
    .filter(f => f.endsWith('.sql'))
    .sort();

  const applyMigration = db.transaction((filename: string, sql: string) => {
    db.exec(sql);
    db.prepare('INSERT INTO migrations (filename) VALUES (?)').run(filename);
  });

  for (const file of files) {
    if (!applied.has(file)) {
      const sql = readFileSync(join(migrationsDir, file), 'utf-8');
      applyMigration(file, sql);
      console.log(`Applied migration: ${file}`);
    }
  }
}

const db = new Database('app.db');
runMigrations(db, './migrations');

Dieser Ansatz braucht kein ORM-Framework. Migration-Dateien heißen 001_create_users.sql, 002_add_posts.sql — die Reihenfolge ist durch den Dateinamen garantiert.

7. Wie Claude Code die SQLite-Entwicklung beschleunigt

Claude Code ist kein SQL-Generator, der blind Code produziert — es liest deinen bestehenden Code, versteht das Schema und macht konkrete Vorschläge die in deinen Kontext passen.

Schema-Design: Beschreibe deine Entitäten und deren Beziehungen in normaler Sprache. Claude Code entwirft das Schema mit korrekten Foreign Keys, sinnvollen Datentypen und notwendigen Indexen — und erklärt die Entscheidungen.

claude "Entwirf ein SQLite-Schema für eine To-Do-App mit Benutzern,
Listen und Aufgaben. Aufgaben gehören zu Listen, Listen gehören zu Benutzern.
Aufgaben haben einen Fälligkeitstermin und einen Erledigt-Status."

Query-Optimierung: Slow Queries finden ist mühsam. Übergib die Query und das Schema, und Claude Code analysiert den Ausführungsplan und schlägt Indexe oder Query-Umstrukturierungen vor.

claude "Diese Query ist langsam auf 100.000 Zeilen. Analysiere den
EXPLAIN QUERY PLAN und schlage Optimierungen vor:
SELECT u.name, COUNT(p.id) as post_count
FROM users u LEFT JOIN posts p ON p.user_id = u.id
WHERE u.created_at > 1700000000
GROUP BY u.id
ORDER BY post_count DESC"

Migration-Generierung: Schemaänderungen formulieren — Claude Code schreibt die Migration inklusive dem SQLite-üblichen Tanz für Column-Änderungen (neue Tabelle, Daten kopieren, umbenennen).

Zwei verwandte Artikel die auf diesem Thema aufbauen:


Claude Code Mastery — von SQLite bis zum produktiven Agenten

Datenbankentwicklung ist eine Stärke von Claude Code — aber nicht die einzige. Im Kurs lernst du Agents, MCP-Server, Hooks, Multi-Agent-Workflows und mehr. Vollständig auf Deutsch, einmalig bezahlt.

Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

Kurs · Claude Code Mastery

Von SQLite bis zum produktiven AI-Agenten

Datenbanken. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.

Jetzt einsteigen → Kursübersicht ansehen →

Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht