DEV Community

Alex Spinov
Alex Spinov

Posted on

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

TypeScript has great types but no standard library for error handling, concurrency, or dependency injection. Effect fixes all three.

What Is Effect?

Effect is a TypeScript library that gives you typed errors, structured concurrency, dependency injection, retries, timeouts, and more — all composable and type-safe.

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(\`The answer is \${result}\`)
  return result
})

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

Typed Errors: No More Try-Catch Guessing

import { Effect, Data } from "effect"

class UserNotFound extends Data.TaggedError("UserNotFound")<{
  readonly userId: string
}> {}

class DatabaseError extends Data.TaggedError("DatabaseError")<{
  readonly message: string
}> {}

// The type signature TELLS you what can go wrong
const getUser = (id: string): Effect.Effect<User, UserNotFound | DatabaseError> =>
  Effect.gen(function* () {
    const result = yield* queryDatabase(id)
    if (!result) yield* Effect.fail(new UserNotFound({ userId: id }))
    return result
  })

// Handle errors exhaustively — compiler ensures you handle all cases
const handled = getUser("123").pipe(
  Effect.catchTag("UserNotFound", (e) => Effect.succeed(defaultUser)),
  Effect.catchTag("DatabaseError", (e) => Effect.die(e))
)
Enter fullscreen mode Exit fullscreen mode

The function signature tells you: this can fail with UserNotFound or DatabaseError. TypeScript enforces you handle both.

Retries and Timeouts

import { Effect, Schedule } from "effect"

const resilientFetch = fetchData.pipe(
  Effect.retry(Schedule.exponential("100 millis").pipe(
    Schedule.compose(Schedule.recurs(3))
  )),
  Effect.timeout("5 seconds"),
  Effect.withSpan("fetch-data") // automatic tracing
)
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[]>
}>() {}

const program = Effect.gen(function* () {
  const db = yield* Database
  return yield* db.query("SELECT * FROM users")
})

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

Effect.runPromise(program.pipe(Effect.provide(DatabaseLive)))
Enter fullscreen mode Exit fullscreen mode

Swap DatabaseLive for DatabaseTest in tests. Zero code changes.

Why Effect

  • Typed errors — know what fails at compile time
  • Structured concurrency — fibers, racing, interruption
  • Dependency injection — built-in, type-safe
  • Observability — tracing, metrics, logging built in
  • Schema — runtime validation with type inference

Building robust TypeScript apps? Check out my developer tools or email spinov001@gmail.com.

Top comments (0)