DEV Community

Alex Spinov
Alex Spinov

Posted on

Effect-TS Has a Free API: TypeScript's Missing Standard Library for Production Apps

Effect is a TypeScript library that provides a standard library for production applications — typed errors, dependency injection, concurrency, retries, and observability all with full type safety.

Why Effect Matters

TypeScript has no standard way to handle: typed errors, dependency injection, retries, rate limiting, or structured concurrency. Effect provides all of these with a composable, type-safe API.

What you get for free:

  • Typed errors (know exactly what can fail)
  • Dependency injection (no container needed)
  • Structured concurrency (fibers, semaphores)
  • Retry policies with backoff
  • Schema validation (like Zod but integrated)
  • Streams (reactive data processing)
  • Built-in tracing and metrics

Quick Start

npm install effect
Enter fullscreen mode Exit fullscreen mode
import { Effect, Console } from "effect";

const program = Effect.gen(function* () {
  yield* Console.log("Hello from Effect!");
  const result = yield* Effect.succeed(42);
  yield* Console.log(`Result: ${result}`);
});

Effect.runPromise(program);
Enter fullscreen mode Exit fullscreen mode

Typed Errors

import { Effect, Data } from "effect";

class NotFoundError extends Data.TaggedError("NotFoundError")<{
  id: string;
}> {}

class DatabaseError extends Data.TaggedError("DatabaseError")<{
  cause: unknown;
}> {}

const findUser = (id: string): Effect.Effect<User, NotFoundError | DatabaseError> =>
  Effect.gen(function* () {
    const user = yield* queryDatabase(id);
    if (!user) {
      return yield* new NotFoundError({ id });
    }
    return user;
  });

// Handle specific errors
const program = findUser("123").pipe(
  Effect.catchTag("NotFoundError", (e) =>
    Effect.succeed({ name: "Default", id: e.id })
  ),
  Effect.catchTag("DatabaseError", (e) =>
    Effect.fail(new Error(`DB failed: ${e.cause}`))
  ),
);
Enter fullscreen mode Exit fullscreen mode

Dependency Injection

import { Effect, Context, Layer } from "effect";

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

class Logger extends Context.Tag("Logger")<
  Logger,
  { log: (msg: string) => Effect.Effect<void> }
>() {}

const findUsers = Effect.gen(function* () {
  const db = yield* Database;
  const logger = yield* Logger;
  yield* logger.log("Fetching users...");
  return yield* db.query("SELECT * FROM users");
});

// Provide implementations
const DatabaseLive = Layer.succeed(Database, {
  query: (sql) => Effect.succeed([{ id: 1, name: "Alice" }]),
});

const LoggerLive = Layer.succeed(Logger, {
  log: (msg) => Effect.sync(() => console.log(msg)),
});

const MainLive = Layer.merge(DatabaseLive, LoggerLive);

Effect.runPromise(
  findUsers.pipe(Effect.provide(MainLive))
);
Enter fullscreen mode Exit fullscreen mode

Retries

import { Effect, Schedule } from "effect";

const fetchData = Effect.tryPromise(() =>
  fetch("https://api.example.com/data").then((r) => r.json())
);

// Retry with exponential backoff
const reliable = fetchData.pipe(
  Effect.retry(
    Schedule.exponential("1 second").pipe(
      Schedule.compose(Schedule.recurs(5))
    )
  ),
);
Enter fullscreen mode Exit fullscreen mode

Concurrency

const tasks = [fetchUser(1), fetchUser(2), fetchUser(3)];

// Run all concurrently
const results = yield* Effect.all(tasks, { concurrency: 3 });

// Run with semaphore (limit concurrency)
const semaphore = yield* Effect.makeSemaphore(5);
const limited = tasks.map((task) => semaphore.withPermits(1)(task));
const results2 = yield* Effect.all(limited);
Enter fullscreen mode Exit fullscreen mode

Links


Building production TypeScript apps? Check out my developer tools on Apify or email spinov001@gmail.com for custom solutions.

Top comments (0)