DEV Community

Alex Spinov
Alex Spinov

Posted on

Effect-TS Has a Free Functional API That Makes TypeScript Bulletproof

Effect-TS brings structured concurrency, typed errors, dependency injection, and resource management to TypeScript — all in one coherent system.

Core: The Effect Type

import { Effect, pipe } from 'effect';

// Effect<Success, Error, Requirements>
const program: Effect.Effect<string, Error, never> = pipe(
  Effect.succeed(42),
  Effect.map(n => n * 2),
  Effect.flatMap(n =>
    n > 50
      ? Effect.succeed(`Big number: ${n}`)
      : Effect.fail(new Error('Too small'))
  )
);

// Run it
Effect.runPromise(program).then(console.log);
Enter fullscreen mode Exit fullscreen mode

Typed Errors (No More try/catch Guessing)

class NotFoundError { readonly _tag = 'NotFoundError'; }
class ValidationError { readonly _tag = 'ValidationError'; constructor(readonly message: string) {} }

const getUser = (id: number): Effect.Effect<User, NotFoundError | ValidationError> =>
  pipe(
    Effect.succeed(id),
    Effect.flatMap(id =>
      id < 0
        ? Effect.fail(new ValidationError('ID must be positive'))
        : Effect.tryPromise({
            try: () => db.findUser(id),
            catch: () => new NotFoundError()
          })
    )
  );

// Handle specific errors
const result = pipe(
  getUser(42),
  Effect.catchTag('NotFoundError', () => Effect.succeed(defaultUser)),
  Effect.catchTag('ValidationError', (e) => Effect.die(e))
);
Enter fullscreen mode Exit fullscreen mode

Concurrency

// Run 3 effects in parallel
const [users, posts, comments] = await Effect.runPromise(
  Effect.all([fetchUsers, fetchPosts, fetchComments], { concurrency: 3 })
);

// Race — first to complete wins
const fastest = Effect.race(fetchFromCDN, fetchFromOrigin);

// Retry with backoff
const resilient = pipe(
  fetchData,
  Effect.retry({
    times: 3,
    schedule: Schedule.exponential('1 second')
  })
);
Enter fullscreen mode Exit fullscreen mode

Dependency Injection (Services)

class Database extends Context.Tag('Database')<Database, {
  query: (sql: string) => Effect.Effect<Row[]>
}>() {}

const getUsers = pipe(
  Database,
  Effect.flatMap(db => db.query('SELECT * FROM users'))
);

// Provide implementation
const program = pipe(
  getUsers,
  Effect.provideService(Database, {
    query: (sql) => Effect.tryPromise(() => pg.query(sql))
  })
);
Enter fullscreen mode Exit fullscreen mode

Why This Matters

  • Typed errors: Know exactly what can fail at compile time
  • Structured concurrency: No dangling promises or race conditions
  • Dependency injection: Test anything by swapping implementations
  • Resource safety: Automatic cleanup with Scope

Need custom TypeScript tooling or robust backend architecture? I build developer tools. Check out my web scraping actors on Apify or reach out at spinov001@gmail.com for custom solutions.

Top comments (0)