Use this file to discover all available pages before exploring further.
This guide walks you through creating a complete Effect application that demonstrates the core concepts: Effects, services, layers, and error handling.
First, create typed errors using Schema.TaggedErrorClass. This makes errors part of your type signatures:
index.ts
import { Effect, Schema } from "effect"// Define a custom error for when a user is not foundexport class UserNotFoundError extends Schema.TaggedErrorClass<UserNotFoundError>()("UserNotFoundError", { userId: Schema.Number}) {}// Define a custom error for database issuesexport class DatabaseError extends Schema.TaggedErrorClass<DatabaseError>()("DatabaseError", { message: Schema.String, cause: Schema.Defect}) {}
Schema.TaggedErrorClass creates serializable error types that work seamlessly with Effect’s error handling.
2
Create a service
Services encapsulate your application’s capabilities. Define a UserRepository service:
index.ts
import { Effect, Layer, ServiceMap } from "effect"// Define a User typeinterface User { readonly id: number readonly name: string readonly email: string}// Create the UserRepository serviceexport class UserRepository extends ServiceMap.Service<UserRepository, { findById(id: number): Effect.Effect<User, UserNotFoundError | DatabaseError> list(): Effect.Effect<Array<User>, DatabaseError>}>()("quickstart/UserRepository") { // Define the service implementation as a Layer static readonly layer = Layer.effect( UserRepository, Effect.gen(function*() { // Simulate a database with in-memory data const users: Map<number, User> = new Map([ [1, { id: 1, name: "Alice", email: "alice@example.com" }], [2, { id: 2, name: "Bob", email: "bob@example.com" }], [3, { id: 3, name: "Charlie", email: "charlie@example.com" }] ]) // Implement the findById method const findById = Effect.fn("UserRepository.findById")( function*(id: number): Effect.fn.Return<User, UserNotFoundError | DatabaseError> { yield* Effect.log("Looking up user", id) const user = users.get(id) if (!user) { return yield* new UserNotFoundError({ userId: id }) } return user } ) // Implement the list method const list = Effect.fn("UserRepository.list")( function*(): Effect.fn.Return<Array<User>, DatabaseError> { yield* Effect.log("Fetching all users") return Array.from(users.values()) } ) // Return the service implementation return UserRepository.of({ findById, list }) }) )}
Effect.fn is used to define functions that return Effects. It provides better stack traces and automatic tracing.
3
Write application logic
Now create your main program that uses the service:
index.ts
// Create a program that fetches and displays a userconst getUserDetails = Effect.fn("getUserDetails")( function*(userId: number) { // Access the UserRepository service const userRepo = yield* UserRepository yield* Effect.log("Starting user lookup") // Fetch the user (this can fail with UserNotFoundError or DatabaseError) const user = yield* userRepo.findById(userId) yield* Effect.log("User found:", user.name) return `User: ${user.name} (${user.email})` })// Create a program that lists all usersconst listAllUsers = Effect.gen(function*() { const userRepo = yield* UserRepository yield* Effect.log("Fetching user list") const users = yield* userRepo.list() yield* Effect.log(`Found ${users.length} users`) return users})
4
Add error handling
Handle errors gracefully with Effect’s error handling combinators:
Effect.catchTag lets you handle specific error types. The error type is removed from the signature after handling.
5
Compose the full program
Combine everything into a main program:
index.ts
const program = Effect.gen(function*() { yield* Effect.log("=== User Management System ===") // Get a specific user const userDetails = yield* safeGetUserDetails yield* Effect.log("Result:", userDetails) // List all users const allUsers = yield* listAllUsers yield* Effect.log("Total users:", allUsers.length) // Try to get a non-existent user const notFound = yield* getUserDetails(999).pipe( Effect.catchTag("UserNotFoundError", () => Effect.succeed("User does not exist") ) ) yield* Effect.log("Not found result:", notFound) return "Program completed successfully"})
6
Provide dependencies and run
Finally, provide the service implementation and run the program:
index.ts
// Provide the UserRepository implementationconst runnable = program.pipe( Effect.provide(UserRepository.layer))// Run the programEffect.runPromise(runnable).then( (result) => console.log("\nFinal result:", result), (error) => console.error("Program failed:", error))
Effect.provide injects the service implementation. Effect.runPromise executes the Effect and returns a Promise.
7
Run your application
Execute your program:
npx tsx index.ts
You should see output like:
timestamp=... level=INFO fiber=#0 message="=== User Management System ==="timestamp=... level=INFO fiber=#0 message="Starting user lookup"timestamp=... level=INFO fiber=#0 message="Looking up user" message=1timestamp=... level=INFO fiber=#0 message="User found:" message=Alicetimestamp=... level=INFO fiber=#0 message=Result: message="User: Alice (alice@example.com)"timestamp=... level=INFO fiber=#0 message="Fetching user list"timestamp=... level=INFO fiber=#0 message="Fetching all users"timestamp=... level=INFO fiber=#0 message="Found 3 users"timestamp=... level=INFO fiber=#0 message="Total users:" message=3...Final result: Program completed successfully
Make sure you’re using yield* (with asterisk) to unwrap Effects, not just yield. The asterisk is required for TypeScript to infer types correctly.
Service not found errors
If you get runtime errors about missing services, ensure you’ve called Effect.provide() with all required service layers before running the program.
Error handling doesn't work
Remember that Effect.catchTag only catches errors that are in the Effect’s error channel. Use the correct error tag name (must match the first parameter to TaggedErrorClass).