Effect-TS
Effect is a powerful TypeScript library for building robust, type-safe applications. It provides a functional effect system with typed errors, dependency injection, resource management, and concurrency primitives.
Why Effect?
Type-safe errors: Unlike Promise which loses error type information, Effect tracks errors in the type system with Effect<Success, Error, Requirements> .
Dependency injection: Services are declared explicitly using Context.Tag and provided via Layer , making dependencies visible in types.
Resource safety: Effect.acquireRelease ensures resources are always cleaned up, even on failure or interruption.
Composability: Effects compose naturally with pipe , Effect.gen generators, and combinators like Effect.all , Effect.flatMap .
Quick Reference
For detailed patterns and examples, see:
-
Core Concepts - Effect type, creating and running effects, generators
-
Error Handling - Typed errors, catchAll, catchTag, Either
-
Services & Layers - Dependency injection with Context.Tag and Layer
-
Schema - Data validation with encode/decode
-
Resources - Resource management with acquireRelease and Scope
The Effect Type
The core type Effect<Success, Error, Requirements> represents a computation that:
-
Produces a value of type Success on success
-
May fail with an error of type Error
-
Requires a context of type Requirements to run
import { Effect } from "effect"
// Effect<number, never, never> - succeeds with number, cannot fail, no requirements const succeed = Effect.succeed(42)
// Effect<never, string, never> - always fails with string error const fail = Effect.fail("Something went wrong")
// Effect<string, Error, never> - may succeed with string or fail with Error
const parse = (input: string): Effect.Effect<number, Error> =>
Effect.try({
try: () => JSON.parse(input),
catch: (e) => new Error(Parse failed: ${e})
})
Creating Effects
import { Effect } from "effect"
// From synchronous values const fromValue = Effect.succeed(42) const fromThunk = Effect.sync(() => Date.now())
// From failures const fromError = Effect.fail(new Error("Failed")) const fromDie = Effect.die("Unexpected error") // Defect, not typed
// From async operations
const fromPromise = Effect.tryPromise({
try: () => fetch("/api/data").then(r => r.json()),
catch: (e) => new Error(Fetch failed: ${e})
})
// From nullable values const fromNullable = Effect.fromNullable(maybeValue)
Running Effects
import { Effect } from "effect"
const program = Effect.succeed(42)
// Synchronous (throws if effect is async or fails) const result = Effect.runSync(program) // 42
// Promise-based const promise = Effect.runPromise(program) // Promise<42>
// With exit status const exit = Effect.runSyncExit(program) const exitPromise = Effect.runPromiseExit(program)
Effect.gen Generators
Write async-looking code with full type safety using generators:
import { Effect } from "effect"
const program = Effect.gen(function* () { const user = yield* fetchUser(userId) const posts = yield* fetchPosts(user.id) const enriched = yield* enrichPosts(posts) return { user, posts: enriched } })
// Equivalent to: const programPipe = fetchUser(userId).pipe( Effect.flatMap(user => fetchPosts(user.id).pipe( Effect.flatMap(posts => enrichPosts(posts).pipe( Effect.map(enriched => ({ user, posts: enriched })) ) ) ) ) )
Error Handling
Effect tracks errors in the type system, making error handling explicit:
import { Effect } from "effect"
class UserNotFound extends Error {
readonly _tag = "UserNotFound"
constructor(readonly userId: string) {
super(User not found: ${userId})
}
}
class DatabaseError extends Error { readonly _tag = "DatabaseError" constructor(readonly cause: unknown) { super("Database error") } }
// Effect<User, UserNotFound | DatabaseError, never> const getUser = (id: string) => Effect.gen(function* () { const record = yield* queryDatabase(id) if (!record) { return yield* Effect.fail(new UserNotFound(id)) } return record })
// Handle specific errors const handled = getUser("123").pipe( Effect.catchTag("UserNotFound", (e) => Effect.succeed({ id: e.userId, name: "Anonymous" }) ) )
// Handle all errors const recovered = getUser("123").pipe( Effect.catchAll((error) => Effect.succeed({ id: "unknown", name: "Fallback" }) ) )
Services and Dependency Injection
Define services with Context.Tag and provide them with Layer :
import { Context, Effect, Layer } from "effect"
// Define a service interface and tag class Database extends Context.Tag("Database")< Database, { readonly query: (sql: string) => Effect.Effect<unknown[]> readonly execute: (sql: string) => Effect.Effect<void> }
() {}
// Use the service in effects const getUsers = Effect.gen(function* () { const db = yield* Database return yield* db.query("SELECT * FROM users") })
// Create a layer that provides the service
const DatabaseLive = Layer.succeed(Database, {
query: (sql) => Effect.sync(() => {
console.log(Executing: ${sql})
return []
}),
execute: (sql) => Effect.sync(() => {
console.log(Executing: ${sql})
})
})
// Provide the layer to run the effect const runnable = Effect.provide(getUsers, DatabaseLive) Effect.runPromise(runnable)
Schema Validation
Use Effect Schema for type-safe data validation:
import { Schema } from "effect"
// Define a schema const User = Schema.Struct({ id: Schema.String, name: Schema.String, email: Schema.String, age: Schema.Number })
// Infer types from schema type User = Schema.Schema.Type<typeof User>
// Decode unknown data const decoded = Schema.decodeUnknownSync(User)({ id: "123", name: "Alice", email: "alice@example.com", age: 30 })
// Decode with Effect (for better error handling) const decodeEffect = Schema.decodeUnknown(User) const result = decodeEffect({ id: "123", name: "Alice", email: "a@b.com", age: 30 }) // Effect<User, ParseError, never>
Resource Management
Safely manage resources that need cleanup:
import { Effect, Scope } from "effect"
// Define a resource with acquisition and release const withConnection = Effect.acquireRelease( // Acquire Effect.sync(() => { console.log("Opening connection") return { query: (sql: string) => sql } }), // Release (conn) => Effect.sync(() => { console.log("Closing connection") }) )
// Use the resource in a scoped effect const program = Effect.scoped( Effect.gen(function* () { const conn = yield* withConnection return conn.query("SELECT * FROM users") }) )
// Connection is automatically closed after use Effect.runPromise(program)
Common Patterns
Sequential vs Parallel Execution
import { Effect } from "effect"
const tasks = [task1, task2, task3]
// Sequential execution const sequential = Effect.all(tasks, { concurrency: 1 })
// Parallel execution (all at once) const parallel = Effect.all(tasks, { concurrency: "unbounded" })
// Parallel with limit const limited = Effect.all(tasks, { concurrency: 5 })
Retry with Backoff
import { Effect, Schedule } from "effect"
const retried = fetchData.pipe( Effect.retry( Schedule.exponential("100 millis").pipe( Schedule.jittered, Schedule.compose(Schedule.recurs(5)) ) ) )
Timeouts
import { Effect, Duration } from "effect"
const withTimeout = longRunningTask.pipe( Effect.timeout(Duration.seconds(30)) )
Guidelines
-
Use Effect.gen for complex flows - Generators make sequential operations readable
-
Tag errors with _tag
-
Enables catchTag for precise error handling
-
Define services with Context.Tag
-
Makes dependencies explicit and testable
-
Use Layer for service composition - Layers compose and manage lifecycle
-
Prefer Effect.try over manual try/catch - Keeps errors in the Effect channel
-
Use Schema for external data - Validates and transforms API/DB data
-
Scope resources with acquireRelease
-
Guarantees cleanup on success or failure
-
Run effects at the edge - Keep runPromise /runSync at application boundaries
Integration with Other Skills
Effect-TS integrates well with:
-
Functional Patterns - Effect builds on Option/Either concepts
-
Drizzle ORM - Wrap Drizzle queries in Effect for typed errors
-
Fastify - Use Effect for request handling with typed errors
When This Skill Loads
This skill automatically loads when discussing:
-
Effect-TS library usage
-
Typed error handling in TypeScript
-
Dependency injection with Context and Layer
-
Effect.gen generators
-
Schema validation with Effect
-
Resource management and Scope
-
Functional effect systems