Claude Code for Effect-TS: Type-Safe Effects and Error Handling — Claude Skills 360 Blog
Blog / Backend / Claude Code for Effect-TS: Type-Safe Effects and Error Handling
Backend

Claude Code for Effect-TS: Type-Safe Effects and Error Handling

Published: January 23, 2027
Read time: 9 min read
By: Claude Skills 360

Effect is a TypeScript library for building type-safe, composable programs where errors, dependencies, and concurrency are first-class values. Effect<Success, Error, Requirements> encodes what a computation produces, what errors it can raise, and what services it needs. pipe() chains operations without nesting. Effect.gen with yield* reads like async/await but works for any effect. Layer composes dependency injection. Schema provides runtime validation with full TypeScript inference. Stream handles async sequences with backpressure. Claude Code generates Effect programs, service layers, schema validators, and stream pipelines for production TypeScript applications with precise error types.

CLAUDE.md for Effect Projects

## Effect Stack
- Version: effect >= 3.10, @effect/schema >= 0.73 (now @effect/schema bundled in effect)
- Core: Effect<A, E, R> — Success, Error, Requirements tritype
- Composition: pipe() for linear chains, Effect.gen for imperative-style with yield*
- DI: Layer<Services, Error, Requirements> — compose into final layer
- Schema: Schema.decodeUnknown for parsing, Schema.encode for serialization
- Concurrency: Effect.all (parallel), Effect.forEach, Fiber management
- Error: typed tagged errors (class MyError extends Data.TaggedError<"MyError">)

Core Effect Patterns

// src/effects/core.ts — fundamental Effect patterns
import { Effect, pipe, Data, Option, Either } from "effect"

// Tagged errors: precise error types with tags
class OrderNotFoundError extends Data.TaggedError("OrderNotFoundError")<{
  orderId: string
}> {}

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

class ValidationError extends Data.TaggedError("ValidationError")<{
  field: string
  message: string
}> {}

// Effect<Order, OrderNotFoundError | DatabaseError, never>
// = returns Order, might fail with these errors, no services required
const getOrderById = (orderId: string): Effect.Effect<Order, OrderNotFoundError | DatabaseError> =>
  Effect.tryPromise({
    try: async () => {
      const order = await db.orders.findUnique({ where: { id: orderId } })
      if (!order) throw new OrderNotFoundError({ orderId })
      return order
    },
    catch: (error) => {
      if (error instanceof OrderNotFoundError) return error
      return new DatabaseError({ message: String(error), cause: error })
    },
  })


// pipe: compose operations left to right
const processOrder = (orderId: string) =>
  pipe(
    getOrderById(orderId),
    Effect.flatMap((order) =>
      order.status === "pending"
        ? Effect.succeed(order)
        : Effect.fail(new ValidationError({
            field: "status",
            message: `Cannot process order with status: ${order.status}`,
          }))
    ),
    Effect.tap((order) => Effect.log(`Processing order: ${order.id}`)),
    Effect.flatMap((order) => updateOrderStatus(order.id, "processing")),
  )


// Effect.gen: imperative style with full type safety
const createOrderEffect = (
  customerId: string,
  items: OrderItem[]
): Effect.Effect<Order, DatabaseError | ValidationError, OrderService | EmailService> =>
  Effect.gen(function* () {
    const orderService = yield* OrderService
    const emailService = yield* EmailService

    if (items.length === 0) {
      yield* Effect.fail(new ValidationError({ field: "items", message: "Required" }))
    }

    const order = yield* orderService.create(customerId, items)

    yield* emailService.sendConfirmation(order)

    yield* Effect.log(`Order created: ${order.id}`)

    return order
  })

Service Layers

// src/services/order-service.ts — Effect service pattern
import { Effect, Layer, Context, pipe } from "effect"

// Service interface
class OrderService extends Context.Tag("OrderService")<
  OrderService,
  {
    create: (customerId: string, items: OrderItem[]) =>
      Effect.Effect<Order, DatabaseError>
    findById: (id: string) =>
      Effect.Effect<Order, OrderNotFoundError | DatabaseError>
    cancel: (id: string) =>
      Effect.Effect<Order, OrderNotFoundError | DatabaseError | ValidationError>
  }
>() {}

class EmailService extends Context.Tag("EmailService")<
  EmailService,
  {
    sendConfirmation: (order: Order) => Effect.Effect<void, never>
    sendCancellation: (order: Order) => Effect.Effect<void, never>
  }
>() {}

class DatabaseService extends Context.Tag("DatabaseService")<
  DatabaseService,
  { query: <T>(sql: string, params: unknown[]) => Effect.Effect<T[], DatabaseError> }
>() {}


// Layer: provides a service implementation
const OrderServiceLive = Layer.effect(
  OrderService,
  Effect.gen(function* () {
    const db = yield* DatabaseService

    return OrderService.of({
      create: (customerId, items) =>
        Effect.gen(function* () {
          const totalCents = items.reduce(
            (sum, item) => sum + item.priceCents * item.quantity,
            0
          )

          const [order] = yield* db.query<Order>(
            "INSERT INTO orders (customer_id, total_cents, status) VALUES ($1, $2, 'pending') RETURNING *",
            [customerId, totalCents]
          )

          return order
        }),

      findById: (id) =>
        pipe(
          db.query<Order>("SELECT * FROM orders WHERE id = $1", [id]),
          Effect.map(([order]) => order),
          Effect.flatMap((order) =>
            order
              ? Effect.succeed(order)
              : Effect.fail(new OrderNotFoundError({ orderId: id }))
          )
        ),

      cancel: (id) =>
        Effect.gen(function* () {
          const order = yield* OrderService.findById(id)

          if (order.status !== "pending") {
            yield* Effect.fail(new ValidationError({
              field: "status",
              message: `Cannot cancel order with status: ${order.status}`,
            }))
          }

          const [updated] = yield* db.query<Order>(
            "UPDATE orders SET status = 'cancelled' WHERE id = $1 RETURNING *",
            [id]
          )

          return updated
        }),
    })
  })
)

// Compose layers into a full application layer
const AppLayer = Layer.mergeAll(
  OrderServiceLive,
  EmailServiceLive,
  DatabaseServiceLive,
)


// Running effects with a layer
const program = createOrderEffect("cust-123", items)
const runnable = Effect.provide(program, AppLayer)

// Execute the program
const result = await Effect.runPromise(runnable)

Schema Validation

// src/schemas/order-schema.ts — runtime validation with Schema
import { Schema, Effect, pipe } from "effect"

// Define schema with precise types
const OrderItemSchema = Schema.Struct({
  productId: Schema.String.pipe(Schema.minLength(1)),
  productName: Schema.String.pipe(Schema.minLength(1)),
  quantity: Schema.Number.pipe(Schema.positive(), Schema.int()),
  priceCents: Schema.Number.pipe(Schema.positive(), Schema.int()),
})

const CreateOrderRequestSchema = Schema.Struct({
  customerId: Schema.String.pipe(Schema.minLength(1)),
  items: Schema.Array(OrderItemSchema).pipe(Schema.minItems(1)),
  shippingAddress: Schema.Struct({
    line1: Schema.String,
    city: Schema.String,
    country: Schema.String.pipe(Schema.length(2)),
    postalCode: Schema.String,
  }),
})

// Infer TypeScript type from schema
type CreateOrderRequest = Schema.Schema.Type<typeof CreateOrderRequestSchema>

// Decode unknown input with typed errors
const parseCreateOrderRequest = (input: unknown) =>
  pipe(
    Schema.decodeUnknown(CreateOrderRequestSchema)(input),
    Effect.mapError((parseError) =>
      new ValidationError({
        field: "request",
        message: String(parseError),
      })
    )
  )


// API route handler with Effect
const handleCreateOrder = (rawBody: unknown) =>
  Effect.gen(function* () {
    const request = yield* parseCreateOrderRequest(rawBody)
    const order = yield* createOrderEffect(request.customerId, request.items)
    return order
  })

Concurrent Effects

// src/concurrent/parallel.ts — Effect concurrency patterns
import { Effect, pipe, Fiber, Queue } from "effect"

// Process multiple orders in parallel with bounded concurrency
const processBatch = (orderIds: string[]) =>
  Effect.forEach(
    orderIds,
    (id) => processOrder(id),
    { concurrency: 10 }  // Max 10 concurrent
  )

// Race: return first to succeed
const fetchFromAnyReplica = (orderId: string) =>
  Effect.race(
    getOrderFromPrimary(orderId),
    getOrderFromReplica(orderId),
  )

// Parallel with independent error handling
const fetchOrderAndCustomer = (orderId: string) =>
  Effect.all({
    order: getOrderById(orderId),
    customer: getCustomerForOrder(orderId),
  }, { concurrency: "unbounded" })

// Fibers for background tasks
const startBackgroundProcessing = Effect.gen(function* () {
  const fiber = yield* Effect.fork(
    Effect.forever(
      pipe(
        processNextPendingOrder(),
        Effect.catchAll((error) => Effect.log(`Error: ${error._tag}`)),
        Effect.delay("1 second"),
      )
    )
  )

  // Fiber can be interrupted later
  return fiber
})

Error Recovery

// src/effects/recovery.ts — error handling strategies
import { Effect, pipe } from "effect"

// Catch specific error types
const getOrderWithFallback = (orderId: string) =>
  pipe(
    getOrderById(orderId),
    Effect.catchTag("OrderNotFoundError", (_err) =>
      Effect.succeed(null)  // Return null instead of failing
    ),
    Effect.catchTag("DatabaseError", (err) =>
      pipe(
        Effect.logError(`DB error: ${err.message}`),
        Effect.andThen(getOrderFromReadReplica(orderId))
      )
    ),
  )

// Retry with backoff
const reliableDbCall = <A>(effect: Effect.Effect<A, DatabaseError>) =>
  pipe(
    effect,
    Effect.retry({
      times: 3,
      schedule: Schedule.exponential("100 millis"),
    }),
    Effect.catchTag("DatabaseError", (err) =>
      Effect.fail(new DatabaseError({
        message: `Failed after 3 retries: ${err.message}`,
      }))
    ),
  )

For the Zod validation library that provides similar runtime type checking for TypeScript without Effect’s broader effect system, see the Zod guide for schema definitions and API validation. For the fp-ts functional programming alternative that implements the TypeScript HKT pattern Effect builds on, see the functional programming guide for type-safe composition. The Claude Skills 360 bundle includes Effect-TS skill sets covering typed errors, service layers, and Schema validation. Start with the free tier to try Effect program generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free