DEV Community

Alex Spinov
Alex Spinov

Posted on

Effect-TS Has a Free Toolkit for Building Bulletproof TypeScript Applications

TypeScript catches type errors at compile time. But what about runtime failures — network timeouts, missing env vars, malformed API responses?

Effect-TS is a production-grade toolkit that makes TypeScript applications resilient by design. Think of it as Rust's Result type meets Go's structured concurrency — but for TypeScript.

The Problem Effect Solves

// Typical TypeScript — looks safe, isn't
async function getUser(id: string) {
  const response = await fetch(`/api/users/${id}`); // Can throw
  const data = await response.json(); // Can throw
  return data.user; // Can be undefined
}
Enter fullscreen mode Exit fullscreen mode

This function has 3 hidden failure modes. TypeScript won't warn you about any of them.

// With Effect — every failure is visible in the type system
import { Effect, Schema } from "effect";

const User = Schema.Struct({
  id: Schema.String,
  name: Schema.String,
  email: Schema.String,
});

const getUser = (id: string) =>
  Effect.gen(function* () {
    const response = yield* Effect.tryPromise({
      try: () => fetch(`/api/users/${id}`),
      catch: () => new NetworkError({ id }),
    });
    const json = yield* Effect.tryPromise({
      try: () => response.json(),
      catch: () => new ParseError({ id }),
    });
    return yield* Schema.decodeUnknown(User)(json);
  });
// Type: Effect<User, NetworkError | ParseError | ParseResult.ParseError>
Enter fullscreen mode Exit fullscreen mode

Now every failure mode is in the return type. The compiler forces you to handle them.

Key Features (All Free)

1. Typed Errors

class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {}
class Unauthorized extends Data.TaggedError("Unauthorized")<{}> {}

const program = getUser("123").pipe(
  Effect.catchTag("NotFound", (e) => Effect.succeed(defaultUser)),
  Effect.catchTag("Unauthorized", () => Effect.fail(new LoginRequired())),
);
Enter fullscreen mode Exit fullscreen mode

2. Dependency Injection (Services)

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

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

// Provide real or test implementation
const program = getUsers.pipe(
  Effect.provideService(Database, realDatabase),
);
Enter fullscreen mode Exit fullscreen mode

3. Structured Concurrency

const parallel = Effect.all([
  fetchUsers,
  fetchOrders,
  fetchProducts,
], { concurrency: 3 });
// All run in parallel, all errors collected, proper cleanup on failure
Enter fullscreen mode Exit fullscreen mode

4. Built-in Retry & Scheduling

const resilient = fetchData.pipe(
  Effect.retry({
    times: 3,
    schedule: Schedule.exponential("1 second"),
  }),
  Effect.timeout("30 seconds"),
);
Enter fullscreen mode Exit fullscreen mode

Why Developers Are Switching

  • Vercel uses Effect in their internal tools
  • Effect has 8,000+ GitHub stars and growing fast
  • The ecosystem includes HTTP server, SQL, RPC, AI, and more

Getting Started

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

const program = Effect.gen(function* () {
  yield* Console.log("Hello, Effect!");
  return 42;
});

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

Want to build resilient data pipelines? I create robust scraping and API integration solutions. Contact me at spinov001@gmail.com or browse my tools on Apify.

Top comments (0)