DEV Community

Alex Spinov
Alex Spinov

Posted on

Effect-TS Has a Free API You've Never Heard Of

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
);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)));
Enter fullscreen mode Exit fullscreen mode

Quick Start

npm install effect
Enter fullscreen mode Exit fullscreen mode

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)