S SvelteKurs
M10 · Hooks, Auth & Praxis

Drizzle & Postgres

Typsichere Datenbankzugriffe für die DevNotes-App — der ActiveRecord-Ersatz in TypeScript.

Warum Drizzle?

Drizzle ist ein schlankes, typsicheres ORM für TypeScript. Du beschreibst dein Schema in Code, bekommst daraus automatisch Typen für jede Query und generierst SQL-Migrationen. Es läuft nur auf dem Server — perfekt für +page.server.ts und Hooks.

bash
npm install drizzle-orm postgres
npm install -D drizzle-kit

🛤️ Rails-Vergleich: ActiveRecord

Wo Rails Migrationen + Models + schema.rb hat, hat Drizzle eine Schema-Datei als Single Source of Truth. Queries sind explizit (kein „Magic"), dafür vollständig typgeprüft.

Rails ruby
# Rails: Migration + Model + Query
class CreateNotes < ActiveRecord::Migration[7.1]
  def change
    create_table :notes do |t|
      t.references :user, foreign_key: true
      t.string :title, null: false
      t.timestamps
    end
  end
end

Note.where(user: current_user).order(updated_at: :desc)

Das Schema

So sieht das echte DevNotes-Schema dieses Kurses aus:

src/lib/server/db/schema.ts ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  username: text('username').notNull().unique(),
  passwordHash: text('password_hash').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
});

export const notes = pgTable('notes', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
  title: text('title').notNull(),
  body: text('body').notNull().default(''),
  tag: text('tag').notNull().default('allgemein'),
  updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
});

export type Note = typeof notes.$inferSelect;

$inferSelect leitet den TypeScript-Typ direkt aus der Tabelle ab — änderst du das Schema, ändern sich die Typen automatisch mit.

Der Datenbank-Client

src/lib/server/db/index.ts ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import { env } from '$env/dynamic/private';
import * as schema from './schema';

// $env/dynamic/private liest zur LAUFZEIT — wichtig für Docker/Coolify,
// wo DATABASE_URL erst im Container gesetzt wird.
const client = postgres(env.DATABASE_URL!);
export const db = drizzle(client, { schema });
export { schema };

⚠️ Server-only!

Alles unter src/lib/server/ landet nie im Client-Bundle. SvelteKit erzwingt das — ein Import aus einer .svelte-Komponente würde einen Build-Fehler geben. So bleiben DB-Zugangsdaten sicher.

Queries schreiben

ts
import { eq, desc, and } from 'drizzle-orm';
import { db, schema } from '$lib/server/db';

// SELECT * FROM notes WHERE user_id = ? ORDER BY updated_at DESC
const meine = await db.select().from(schema.notes)
  .where(eq(schema.notes.userId, userId))
  .orderBy(desc(schema.notes.updatedAt));

// INSERT ... RETURNING *
const [note] = await db.insert(schema.notes)
  .values({ userId, title: 'Neu' }).returning();

// UPDATE / DELETE mit Besitz-Prüfung im WHERE
await db.update(schema.notes).set({ title: 'Geändert' })
  .where(and(eq(schema.notes.id, id), eq(schema.notes.userId, userId)));

Beachte das Muster and(eq(id), eq(userId)): Die Besitz-Prüfung steckt direkt im WHERE — so kann niemand fremde Notizen ändern oder löschen.

Migrationen

drizzle.config.ts ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/lib/server/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: { url: process.env.DATABASE_URL! }
});
bash
# Migration aus dem Schema erzeugen:
npx drizzle-kit generate
# Migration auf die DB anwenden:
npx drizzle-kit migrate

generate erzeugt aus dem Schema eine SQL-Datei im drizzle/-Ordner, migrate spielt sie ein. In Produktion läuft migrate beim Container-Start (siehe Modul 11).

Zusammenfassung

💡 Das Wichtigste

  • Schema in Code → Typen & Migrationen kommen automatisch.
  • Queries sind explizit und typsicher (eq, and, desc).
  • src/lib/server/ ist garantiert server-only.

🎯 Probier es selbst

Füge der notes-Tabelle ein Feld pinned (boolean, Default false) hinzu und generiere eine neue Migration mit npx drizzle-kit generate.