Effect-TS is a powerful TypeScript library for building type-safe, composable, and concurrent applications. Think of it as a complete standard library for TypeScript — with structured concurrency, dependency injection, error handling, and streaming built in.
What Makes Effect Special?
- Type-safe errors — errors are tracked in the type system
- Structured concurrency — fibers, scopes, and supervision
- Dependency injection — compile-time verified DI
- Streaming — built-in streaming with backpressure
- Retries and scheduling — composable retry policies
The Hidden API: Typed Error Handling
import { Effect, pipe } from 'effect';
class UserNotFound { readonly _tag = 'UserNotFound' as const; }
class DatabaseError { readonly _tag = 'DatabaseError' as const; }
// Errors are PART OF THE TYPE
const getUser = (id: string): Effect.Effect<User, UserNotFound | DatabaseError> =>
pipe(
queryDatabase(id), // Effect<Row, DatabaseError>
Effect.flatMap(row =>
row ? Effect.succeed(parseUser(row)) : Effect.fail(new UserNotFound())
)
);
// Compiler FORCES you to handle every error
const program = pipe(
getUser('123'),
Effect.catchTag('UserNotFound', () => Effect.succeed(defaultUser)),
Effect.catchTag('DatabaseError', (e) => Effect.die(e)) // unrecoverable
);
Structured Concurrency API
import { Effect, Fiber } from 'effect';
// Run tasks concurrently with automatic cleanup
const program = Effect.all([
fetchUserProfile(userId),
fetchUserPosts(userId),
fetchUserFollowers(userId)
], { concurrency: 'unbounded' });
// Race — first to complete wins, others are cancelled
const fastest = Effect.race(
fetchFromPrimary(key),
fetchFromReplica(key)
);
// Fibers — lightweight threads
const fiber = yield* Effect.fork(longRunningTask);
// ... do other work ...
const result = yield* Fiber.join(fiber);
Dependency Injection API
import { Effect, Context, Layer } from 'effect';
// Define service interfaces
class Database extends Context.Tag('Database')<
Database,
{ query: (sql: string) => Effect.Effect<Row[]> }
>() {}
class Cache extends Context.Tag('Cache')<
Cache,
{ get: (key: string) => Effect.Effect<string | null> }
>() {}
// Use services — compiler verifies they're provided
const getUser = (id: string) =>
Effect.gen(function* () {
const cache = yield* Cache;
const cached = yield* cache.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const db = yield* Database;
const [user] = yield* db.query(`SELECT * FROM users WHERE id = $1`);
return user;
});
// Provide implementations via Layers
const MainLayer = Layer.merge(
Layer.succeed(Database, { query: pgQuery }),
Layer.succeed(Cache, { get: redisGet })
);
Effect.runPromise(pipe(getUser('123'), Effect.provide(MainLayer)));
Quick Start
npm install effect
Why Teams Adopt Effect
A senior engineer shared: "We had 47 unhandled promise rejections in production last month. After migrating to Effect, we have zero — because the type system makes it impossible to forget error handling."
Need robust TypeScript tools? Email spinov001@gmail.com or check my toolkit.
Using Effect-TS? What's your approach to error handling in TypeScript?
Top comments (0)