TypeScript ist mächtig — aber Standard-TypeScript reicht für robuste Produktionssysteme oft nicht aus.
Fehlende Fehlertypen in Signaturen, unkontrollierte Seiteneffekte, komplizierte Dependency Injection: Das alles löst Effect-TS radikal. In diesem Artikel zeigen wir, wie Claude Code als KI-Pair-Programmer dir hilft, Effect-TS schnell zu verstehen und produktiv einzusetzen — von Effect-Grundlagen bis zu echten Concurrent-Patterns.
Effect-TS hat sich in den letzten zwei Jahren von einem Nischenprojekt zur ernsthaften Alternative für TypeScript-Backends entwickelt. Wer einmal echte typsichere Fehlerbehandlung kennt — also Fehler im Typsystem statt im Laufzeit-Exception-Stack — will nicht mehr zurück.
🔒
Typsichere Fehler
Fehlertypen erscheinen direkt in der Funktionssignatur. Kein überraschendes throw mehr.
🧩
Dependency Injection
Services als Typen — keine globalen Singletons, kein IoC-Container von Hand.
⚡
Strukturierte Concurrency
Fiber-basierte Parallelität mit sauberem Lifecycle — keine ungekillten Promises.
✅
Schema-Validierung
Eingebaute Validierung, Parsing und Serialisierung — kein Zod extra nötig.
Claude Code + Effect-TS: Claude Code kennt die Effect-API sehr gut. Mit dem richtigen Prompt kannst du komplette Layer-Architekturen, Schema-Definitionen und Fiber-basierte Workflows generieren lassen — und musst die Typen selbst nur noch reviewen statt schreiben.
Lass uns mit dem Setup beginnen. Installation mit npm oder pnpm:
Terminal
npm install effect
# oderpnpm add effect
Das war's — Effect-TS hat keine Peer-Dependencies. Alles ist in einem Package: Effect-Typ, Schema, Layer, Fiber, Concurrency-Utilities.
01 — GrundlagenEffect-Typ, succeed, fail, pipe und runPromise
Der zentrale Typ in Effect-TS ist Effect<A, E, R>. Diese drei Typparameter bedeuten:
A Success-Typ
Was zurückgegeben wird, wenn alles klappt.
E Error-Typ
Welche Fehler können auftreten? Explizit im Typsystem — kein unknown.
R Requirements-Typ
Welche Services/Dependencies braucht dieser Effect? Werden via Layer aufgelöst.
Einfache Effects erstellen
grundlagen.ts
import { Effect } from"effect"// Effect.succeed: Ein Wert ohne Fehler, ohne Requirementsconsthallo: Effect.Effect<string, never, never> = Effect.succeed("Hallo Effect-TS!")
// Effect.fail: Ein typisierter Fehlerconstfehler = Effect.fail(newError("Etwas ist schiefgelaufen"))
// Effect.sync: Synchrone Berechnung mit möglichem throwconstzufallszahl = Effect.sync(() => Math.random())
// Effect.tryPromise: Async-Funktion die ein Promise zurückgibtconstfetchBenutzer = Effect.tryPromise({
try: () => fetch("https://api.example.com/benutzer/1").then(r => r.json()),
catch: (err) => newFetchFehler({ cause: err })
})
pipe: Effects kombinieren
Effects werden nicht direkt ausgeführt — sie sind Beschreibungen von Berechnungen. Mit pipe und Effect.map/Effect.flatMap werden sie kombiniert:
Claude Code Tipp: Frag Claude Code nach "Effect.gen statt pipe" wenn du die async/await-Syntax bevorzugst. Claude kennt beide Stile und kann die idiomatischere Variante für deinen Use Case vorschlagen.
02 — Error-HandlingcatchAll, catchTag und typisierte Fehler
Das stärkste Feature von Effect-TS ist sein Fehlermodell. Fehler werden nicht nur geworfen und irgendwo gefangen — sie sind Teil des Typsystems. Welche Fehler eine Funktion werfen kann, siehst du sofort in IntelliSense.
Definiere Basis-Fehlerklassen für deine Domain (z.B. AppFehler) und leite spezifischere Fehler davon ab. So kannst du mit catchTag granular reagieren oder mit catchAll alle AppFehler auf einmal behandeln.
Claude Code kann dir diese Hierarchie automatisch generieren: "Erstelle eine Effect-TS Fehler-Hierarchie für eine E-Commerce-App mit Checkout-, Inventar- und Zahlungsfehlern."
03 — Dependency InjectionContext.Tag, Layer und Effect.provide
Dependency Injection in TypeScript war bisher entweder zu einfach (globale Singletons) oder zu komplex (IoC-Container wie InversifyJS). Effect-TS löst das elegant mit Context.Tag und Layer.
Layer.mergeAll kombiniert unabhängige Layer. Layer.provide löst Abhängigkeiten zwischen Layern auf — wenn EmailLayer den LoggerLayer braucht, reicht EmailLayer.pipe(Layer.provide(LoggerLayer)).
Das Typsystem prüft, ob alle Dependencies aufgelöst sind. Vergisst du einen Layer, kompiliert der Code nicht.
Effect-TS kommt mit einem eingebauten Schema-System. Keine externe Bibliothek nötig — Schema ist Teil von Effect und integriert sich nahtlos mit dem Effect-Typ.
Effect Schema ist Zod sehr ähnlich — aber direkt im Effect-Ökosystem. Kein Wrapping in Effect.tryPromise nötig. ParseError ist ein vollständig typisierter Fehlertyp. Und Schema.transform ermöglicht bidirektionale Konvertierungen.
05 — Fiber & Concurrencyfork, all, race und interrupt
Concurrency in JavaScript ist durch Promises einfach — aber schwer zu kontrollieren. Wie cancelt man ein Promise? Wie wartet man auf mehrere Promises und unterbricht den Rest wenn eines fertig ist? Effect-TS löst das mit Fibers: leichtgewichtigen, unterbrechbaren Green Threads.
Fiber.fork: Parallele Ausführung
fibers.ts
import { Effect, Fiber } from"effect"// Effect.fork: Effect im Hintergrund starten, gibt Fiber zurückconstparalleleAufgaben = Effect.gen(function* () {
// Beide starten sofort, ohne auf das Ergebnis zu wartenconstfaser1 = yield* Effect.fork(ladeBenutzerDaten())
constfaser2 = yield* Effect.fork(ladeProduktDaten())
// Hier kann weiteres Werk erledigt werden...yield* bereiteAntwortStrukturVor()
// Jetzt auf die Ergebnisse wartenconst benutzer = yield* Fiber.join(faser1)
const produkte = yield* Fiber.join(faser2)
return { benutzer, produkte }
})
// Fiber.interrupt: Sauber abbrechenconstmitTimeout = Effect.gen(function* () {
constfaser = yield* Effect.fork(langsameBerechnunng())
yield* Effect.sleep("5 seconds")
// Nach 5 Sekunden: Fiber sauber stoppenconst ergebnis = yield* Fiber.interrupt(faser)
console.log("Fiber unterbrochen:", ergebnis._tag)
})
Promises können nicht gecancelt werden. Fibers schon. Wenn ein Fiber-Elternteil stirbt, werden alle Kind-Fibers automatisch unterbrochen. Das verhindert Memory Leaks und Zombie-Tasks in langlaufenden Servern.
import { Effect, Layer } from"effect"import { describe, it, expect } from"vitest"// Test-Layer: Alle Services gemocktconstTestLayer = Layer.mergeAll(
InMemoryDatenbankLayer, // Echter DB-Code, In-Memory-Daten
MockEmailLayer, // Emails werden nur geloggt
ConsoleLoggerLayer // Echter Logger
)
describe("BenutzerRegistrierung", () => {
it("registriert Benutzer erfolgreich", async () => {
const ergebnis = await Effect.runPromise(
registriereBenutzer("test@test.de", "Test User").pipe(
Effect.provide(TestLayer) // Swap: Prod → Test Layer
)
)
expect(ergebnis.email).toBe("test@test.de")
expect(ergebnis.id).toBeDefined()
})
it("schlägt bei ungültiger Email fehl", async () => {
const ergebnis = await Effect.runPromise(
registriereBenutzer("keine-email", "Test").pipe(
Effect.provide(TestLayer),
Effect.flip // Fehler wird zum Erfolg, Erfolg zum Fehler
)
)
expect(ergebnis._tag).toBe("ValidationFehler")
})
})
Fazit: Das Layer-System macht Testing trivial. Kein jest.mock, kein vi.spyOn auf globale Imports — einfach den Test-Layer statt des Prod-Layers providieren. Der Code wird dadurch automatisch testbar, weil Dependencies nie hartcodiert sind.
Starte jetzt dein kostenloses Trial und entdecke, wie Claude Code dir beim Aufbau typsicherer Effect-TS-Architekturen hilft — von Layer-Design bis zu Schema-Definitionen.
Effect-TS ist kein kleines Utility-Framework — es ist eine vollständige Neukonzeption, wie TypeScript-Backends gebaut werden. Die wichtigsten Erkenntnisse:
Effect<A, E, R> macht Fehler und Dependencies explizit — das Typsystem erzwingt korrekten Umgang
Tagged Errors mit Data.TaggedError erlauben granulares Error-Handling per catchTag
Schema validiert, parst und serialisiert — direkt integriert in Effect-Pipelines
Fiber ermöglicht strukturierte Concurrency mit echtem Cancellation — kein Memory Leak durch Zombie-Promises
Testing wird trivial durch Layer-Swap — keine Mocks, keine globalen State-Probleme
Lernkurve: Effect-TS ist mächtig, aber die Lernkurve ist real. Empfehlung: Starte mit Effect.gen statt pipe, nutze Claude Code als Pair-Programmer und migriere incrementell — nicht alles auf einmal.