DEV Community

Alex Spinov
Alex Spinov

Posted on

Effect-TS Has a Free API That Brings Functional Programming to Production TypeScript

Effect is a TypeScript library for building robust, composable applications. Think of it as a type-safe runtime for managing errors, concurrency, dependencies, and resources.

The Effect Type: Errors as Values

import { Effect, pipe } from "effect";

// Effect<Success, Error, Requirements>
const fetchUser = (id: string): Effect.Effect<User, HttpError | NotFoundError> =>
  pipe(
    Effect.tryPromise({
      try: () => fetch(`/api/users/${id}`).then(r => r.json()),
      catch: () => new HttpError(),
    }),
    Effect.flatMap((data) =>
      data ? Effect.succeed(data) : Effect.fail(new NotFoundError(id))
    )
  );

// Errors are tracked in the type system!
// const result: Effect<User, HttpError | NotFoundError>
Enter fullscreen mode Exit fullscreen mode

Pipe: Compose Everything

const program = pipe(
  fetchUser("123"),
  Effect.map(user => user.email),
  Effect.flatMap(email => sendWelcomeEmail(email)),
  Effect.retry({ times: 3 }),
  Effect.timeout("5 seconds"),
  Effect.catchTag("NotFoundError", () => Effect.succeed("default@email.com")),
  Effect.tap(result => Effect.log(`Result: ${result}`)),
);

// Run it
const result = await Effect.runPromise(program);
Enter fullscreen mode Exit fullscreen mode

Concurrency: Structured and Safe

// Run in parallel with concurrency limit
const scrapeAll = Effect.forEach(
  urls,
  (url) => scrapeUrl(url),
  { concurrency: 10 } // Max 10 concurrent
);

// Race — first to succeed wins
const fastest = Effect.raceAll([
  fetchFromCDN1(url),
  fetchFromCDN2(url),
  fetchFromCDN3(url),
]);

// All must succeed
const [users, products, orders] = await Effect.runPromise(
  Effect.all([fetchUsers, fetchProducts, fetchOrders], { concurrency: "unbounded" })
);
Enter fullscreen mode Exit fullscreen mode

Dependency Injection: Layer System

import { Context, Layer } from "effect";

// Define a service
class Database extends Context.Tag("Database")<Database, {
  query: (sql: string) => Effect.Effect<Row[]>;
}>() {}

// Implement it
const PostgresLayer = Layer.succeed(Database, {
  query: (sql) => Effect.tryPromise(() => pool.query(sql).then(r => r.rows)),
});

// Use it (type-safe requirement)
const getUsers = Database.pipe(
  Effect.flatMap(db => db.query("SELECT * FROM users")),
);
// Type: Effect<Row[], SqlError, Database> — requires Database!

// Provide the implementation
const program = getUsers.pipe(Effect.provide(PostgresLayer));
// Type: Effect<Row[], SqlError> — Database requirement satisfied!
Enter fullscreen mode Exit fullscreen mode

Schedule: Retry and Repeat

import { Schedule } from "effect";

// Exponential backoff with jitter
const retryPolicy = pipe(
  Schedule.exponential("100 millis"),
  Schedule.jittered,
  Schedule.compose(Schedule.recurs(5)),
);

const robust = pipe(
  scrapeUrl(url),
  Effect.retry(retryPolicy),
);

// Repeat on interval
const monitor = pipe(
  checkPrice(productUrl),
  Effect.repeat(Schedule.spaced("1 hour")),
);
Enter fullscreen mode Exit fullscreen mode

Stream: Process Data Incrementally

import { Stream } from "effect";

const dataStream = pipe(
  Stream.fromIterable(urls),
  Stream.mapEffect((url) => scrapeUrl(url), { concurrency: 5 }),
  Stream.filter(result => result.price < 50),
  Stream.tap(result => Effect.log(`Found: ${result.title}`)),
  Stream.runCollect,
);
Enter fullscreen mode Exit fullscreen mode

Build robust scraping pipelines? My Apify tools handle the scraping, Effect handles the orchestration.

Custom pipeline? Email spinov001@gmail.com

Top comments (0)