
Bun.js + Elysia + Drizzle: eine typsichere REST-API mit PostgreSQL
Ein praktisches Bun.js-API mit Elysia für Routing, Drizzle ORM für typisierte SQL-Abfragen, PostgreSQL und Zod-Validierung.
Elysia verarbeitet HTTP-Routen, Drizzle beschreibt PostgreSQL-Tabellen mit TypeScript und Bun führt den gesamten Service ohne separaten Transpilierungsschritt aus.
Warum dieser Stack
Jede Bibliothek hat eine klare Aufgabe — das hält den Service testbar und erweiterbar.
Elysia
Ein Bun-natives HTTP-Framework mit Routen, Middleware und Schemas.
Drizzle
Ein SQL-first ORM: Tabellen sind TypeScript, Abfragen bleiben nah an SQL.
PostgreSQL
Ein zuverlässiges Production-Storage für Indizes, Transaktionen und Wachstum.
Zod
Validiert Eingaben an der API-Grenze vor der Service-Schicht.
Die Anfrage wird vor SQL validiert; die Antwort folgt einem typisierten Vertrag.
Screenshot des Abschnitts stackSchritt 1: Projekt erstellen
Bun installiert Abhängigkeiten und führt TypeScript ohne separaten Bundler aus.
Initialisieren
mkdir tasks-api && cd tasks-api
bun init
bun add elysia drizzle-orm postgres zod
bun add -d drizzle-kitUmgebung konfigurieren
DATABASE_URL=postgres://app:app@localhost:5432/tasks
PORT=3000
DB_POOL_SIZE=10Bun lädt .env automatisch. Geheimnisse gehören nicht ins Repository.
Migrationen konfigurieren
import { defineConfig } from "drizzle-kit";
export default defineConfig({ schema: "./src/db/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL! } });Danach bun run db:generate und bun run db:migrate ausführen.
Schritt 2: Tabelle und Migration definieren
Die Tabelle ist die Quelle für Insert- und Select-Typen. Einen zweiten manuellen Task-Typ sollten wir vermeiden.
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
export const tasks = pgTable("tasks", { id: uuid("id").defaultRandom().primaryKey(), title: text("title").notNull(), done: boolean("done").notNull().default(false), createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull() });
export type Task = typeof tasks.$inferSelect;
export type NewTask = typeof tasks.$inferInsert;Die generierte Migration wird mit bun run db:migrate angewendet.
Schritt 3: typsichere Elysia-Routen
Elysia validiert Payloads am Rand der Anwendung. Der Handler kann sich dadurch auf die Datenbankoperation konzentrieren.
import { Elysia, t } from "elysia";
import { desc, eq } from "drizzle-orm";
import { db } from "./db/client";
import { tasks } from "./db/schema";
export const app = new Elysia()
.get("/health", () => ({ status: "ok", runtime: "bun" }))
.get("/tasks", () => db.select().from(tasks).orderBy(desc(tasks.createdAt)))
.patch("/tasks/:id", async ({ params, body }) => { const [task] = await db.update(tasks).set({ done: body.done }).where(eq(tasks.id, params.id)).returning(); return task ?? new Response("Nicht gefunden", { status: 404 }); }, { params: t.Object({ id: t.String() }), body: t.Object({ done: t.Boolean() }) })
.listen(Number(Bun.env.PORT ?? 3000));Database Client und Docker Compose
Erzeuge einen Pool pro langlebigem Bun-Prozess. Compose stellt lokal für das ganze Team dieselbe PostgreSQL-Version bereit.
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
const sql = postgres(Bun.env.DATABASE_URL!, { max: Number(Bun.env.DB_POOL_SIZE ?? 10), prepare: false });
export const db = drizzle(sql);services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: tasks
ports: ["5432:5432"]Transaktionen, Fehler und Production-Regeln
Ein schneller Runtime ersetzt keine klaren Datengrenzen.
Transaktionen für verbundene Writes
Mehrere Tabellen mit db.transaction(async (tx) => { ... }) atomar ändern.
404 für fehlende Ressourcen
Das Ergebnis von returning() prüfen und nicht undefined mit 200 senden.
Migrationen separat ausführen
Migrationen als Deploy-Schritt anwenden, nicht aus jeder Server-Instanz.
Pool begrenzen
In Serverless-Umgebungen Pooler oder niedrigere Limits verwenden.
Handler ohne offenen Port testen
Mit handle lässt sich die Anwendung direkt testen.
import { expect, test } from "bun:test";
import { app } from "./index";
test("lehnt einen leeren Titel ab", async () => {
const response = await app.handle(new Request("http://localhost/tasks", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ title: " " }) }));
expect(response.status).toBe(422);
});Mit bun test gegen eine eigene Testdatenbank ausführen.
Lokal ausführen und testen
PostgreSQL starten
docker compose up -d
bun run db:migrateBun starten
bun run src/index.ts
curl http://localhost:3000/healthLockfile verwenden
bun install --frozen-lockfile
bun run db:migrate
bun testFAQ
Ja. Drizzle funktioniert mit Bun und PostgreSQL über den postgres-Treiber oder einen unterstützten Treiber. Die verwendeten Versionen sollten im Deployment getestet werden.
Nein. Bun stellt Runtime, Package Manager und TypeScript-Ausführung bereit.
Fazit
Beschreiben Sie die Aufgabe — die ersten 15 Minuten der Beratung sind kostenlos.
Verwandte Artikel

Mau Saver zu einer Telegram-Gruppe hinzufügen und Medien im Chat speichern
Praktische Anleitung: Mau Saver zu einer Telegram-Gruppe hinzufügen und Videos, Audio und Posts direkt im Chat herunterladen.

Internationale Telegram-Media-Zielgruppen mit Werbung erreichen
Praktischer Leitfaden für Werbetreibende: Mau Saver, Telegram-Gruppen, angeheftete Beiträge und native Medienintegrationen.

AI Assistant Entwicklung Kosten 2026: RAG, Knowledge Base, Integrationen und Support
Praktischer Leitfaden zu Kosten fuer AI Assistants: RAG, Knowledge Base, Channels, Tool Use, Guardrails, Evaluations, Monitoring und Support.

KI kann mehr erzeugen. Nicht besser: Was die Forschung über Spieleentwicklung sagt
Generative KI kommt in die Spieleproduktion, doch die Fakten sind nuancierter als der Hype. Wir betrachten Adoption, Spielervertrauen, Qualitätsrisiken und einen sinnvollen Workflow.