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.
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: 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:
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
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
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
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! }
});# Migration aus dem Schema erzeugen:
npx drizzle-kit generate
# Migration auf die DB anwenden:
npx drizzle-kit migrategenerate 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.