Skip to main content
Effect’s Context system enables type-safe dependency injection, allowing you to define requirements and provide implementations separately.

Context.Tag - Define a Service

export interface Tag<in out Id, in out Value> extends Effect<Value, never, Id>
A Tag represents a service that can be provided to effects:
import { Context, Effect } from "effect"

class Database extends Context.Tag("Database")<
  Database,
  {
    readonly query: (sql: string) => Effect.Effect<Array<unknown>>
  }
>() {}
1
Tag Structure
2
The Context.Tag constructor takes:
3
  • Key: A unique string identifier
  • Type Parameters:
    • Id: The service identifier type (usually the class itself)
    • Service: The service interface/implementation type
  • 4
    Using Tags
    5
    Tags are effects that require the service they represent:
    6
    import { Effect } from "effect"
    
    const program = Effect.gen(function* () {
      const db = yield* Database  // Get Database from context
      const users = yield* db.query("SELECT * FROM users")
      return users
    })
    // Effect<Array<unknown>, never, Database>
    //                               ^^^^^^^^ requires Database
    

    Creating Services

    1
    Define the Interface
    2
    import { Context, Effect } from "effect"
    
    interface LoggerService {
      readonly log: (message: string) => Effect.Effect<void>
      readonly error: (message: string) => Effect.Effect<void>
    }
    
    class Logger extends Context.Tag("Logger")<Logger, LoggerService>() {}
    
    3
    Implement the Service
    4
    import { Effect } from "effect"
    
    const consoleLogger: LoggerService = {
      log: (message) => Effect.sync(() => console.log(message)),
      error: (message) => Effect.sync(() => console.error(message))
    }
    
    const fileLogger: LoggerService = {
      log: (message) => Effect.sync(() => fs.appendFileSync("log.txt", message)),
      error: (message) => Effect.sync(() => fs.appendFileSync("error.txt", message))
    }
    
    5
    Use the Service
    6
    import { Effect } from "effect"
    
    const program = Effect.gen(function* () {
      const logger = yield* Logger
      yield* logger.log("Application started")
      yield* logger.error("Something went wrong")
    })
    // Effect<void, never, Logger>
    

    provideService - Provide Implementations

    export const provideService: {
      <I, S>(tag: Context.Tag<I, S>, service: S): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, I>>
      <A, E, R, I, S>(self: Effect<A, E, R>, tag: Context.Tag<I, S>, service: S): Effect<A, E, Exclude<R, I>>
    }
    
    Provide a service implementation to an effect:
    import { Effect } from "effect"
    
    const program = Effect.gen(function* () {
      const logger = yield* Logger
      yield* logger.log("Hello, World!")
    })
    // Effect<void, never, Logger>
    
    const runnable = program.pipe(
      Effect.provideService(Logger, consoleLogger)
    )
    // Effect<void, never, never> - Logger requirement satisfied
    
    Effect.runPromise(runnable)
    
    Notice how provideService removes the service from the requirements type: Logger becomes never after providing.

    Multiple Services

    import { Context, Effect } from "effect"
    
    class Database extends Context.Tag("Database")<
      Database,
      { query: (sql: string) => Effect.Effect<Array<unknown>> }
    >() {}
    
    class Logger extends Context.Tag("Logger")<
      Logger,
      { log: (msg: string) => Effect.Effect<void> }
    >() {}
    
    class Config extends Context.Tag("Config")<
      Config,
      { apiUrl: string; timeout: number }
    >() {}
    
    const program = Effect.gen(function* () {
      const db = yield* Database
      const logger = yield* Logger
      const config = yield* Config
    
      yield* logger.log(`Connecting to ${config.apiUrl}`)
      const users = yield* db.query("SELECT * FROM users")
      return users
    })
    // Effect<Array<unknown>, never, Database | Logger | Config>
    
    1
    Providing Multiple Services
    2
    Chain provideService calls:
    3
    import { Effect } from "effect"
    
    const runnable = program.pipe(
      Effect.provideService(Database, dbImpl),
      Effect.provideService(Logger, loggerImpl),
      Effect.provideService(Config, configImpl)
    )
    // Effect<Array<unknown>, never, never>
    
    4
    Use Context.make for multiple services:
    5
    import { Effect, Context } from "effect"
    
    const context = Context.empty().pipe(
      Context.add(Database, dbImpl),
      Context.add(Logger, loggerImpl),
      Context.add(Config, configImpl)
    )
    
    const runnable = Effect.provide(program, context)
    // Effect<Array<unknown>, never, never>
    

    provideServiceEffect - Dynamic Service Creation

    export const provideServiceEffect: {
      <I, S, E1, R1>(
        tag: Context.Tag<I, S>,
        effect: Effect<S, E1, R1>
      ): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E | E1, R1 | Exclude<R, I>>
    }
    
    Provide a service whose creation is itself an effect:
    import { Effect } from "effect"
    
    class Database extends Context.Tag("Database")<
      Database,
      { query: (sql: string) => Effect.Effect<Array<unknown>> }
    >() {}
    
    // Database initialization is an effect
    const createDatabase = Effect.gen(function* () {
      yield* Effect.log("Connecting to database...")
      const connection = yield* Effect.tryPromise(() =>
        pg.connect("postgresql://localhost/mydb")
      )
      yield* Effect.log("Database connected")
    
      return {
        query: (sql) => Effect.tryPromise(() => connection.query(sql))
      }
    })
    
    const program = Effect.gen(function* () {
      const db = yield* Database
      return yield* db.query("SELECT * FROM users")
    })
    
    const runnable = program.pipe(
      Effect.provideServiceEffect(Database, createDatabase)
    )
    
    Effect.runPromise(runnable)
    // Output:
    // Connecting to database...
    // Database connected
    

    Layers - Composable Service Construction

    Layers provide a more powerful abstraction for building and composing services:
    import { Effect, Context, Layer } from "effect"
    
    class Logger extends Context.Tag("Logger")<
      Logger,
      { log: (msg: string) => Effect.Effect<void> }
    >() {}
    
    class Database extends Context.Tag("Database")<
      Database,
      { query: (sql: string) => Effect.Effect<Array<unknown>> }
    >() {}
    
    // Logger layer has no dependencies
    const LoggerLive = Layer.succeed(
      Logger,
      {
        log: (msg) => Effect.sync(() => console.log(msg))
      }
    )
    
    // Database layer depends on Logger
    const DatabaseLive = Layer.effect(
      Database,
      Effect.gen(function* () {
        const logger = yield* Logger
        yield* logger.log("Creating database connection")
    
        return {
          query: (sql) => Effect.gen(function* () {
            yield* logger.log(`Executing: ${sql}`)
            return []
          })
        }
      })
    )
    
    // Compose layers
    const AppLayer = DatabaseLive.pipe(
      Layer.provide(LoggerLive)
    )
    
    const program = Effect.gen(function* () {
      const db = yield* Database
      return yield* db.query("SELECT * FROM users")
    })
    
    const runnable = Effect.provide(program, AppLayer)
    
    Layers enable:
    • Dependency graphs: Automatically resolve service dependencies
    • Resource management: Acquire and release resources safely
    • Sharing: Services are created once and shared across the application
    • Memoization: Layer construction is memoized

    Testing with Services

    import { Effect, Context } from "effect"
    
    class EmailService extends Context.Tag("EmailService")<
      EmailService,
      { send: (to: string, body: string) => Effect.Effect<void> }
    >() {}
    
    const sendWelcomeEmail = (email: string) =>
      Effect.gen(function* () {
        const emailService = yield* EmailService
        yield* emailService.send(email, "Welcome!")
      })
    
    // Production implementation
    const liveEmailService = {
      send: (to, body) =>
        Effect.tryPromise(() => sendEmailViaSmtp(to, body))
    }
    
    // Test implementation
    const testEmailService = {
      send: (to, body) =>
        Effect.sync(() => console.log(`Mock email to ${to}: ${body}`))
    }
    
    // In production
    const prod = sendWelcomeEmail("[email protected]").pipe(
      Effect.provideService(EmailService, liveEmailService)
    )
    
    // In tests
    const test = sendWelcomeEmail("[email protected]").pipe(
      Effect.provideService(EmailService, testEmailService)
    )
    

    Best Practices

    1
    Define Service Interfaces
    2
    Always define explicit service interfaces:
    3
    // ✅ Good: Clear interface
    interface DatabaseService {
      readonly query: (sql: string) => Effect.Effect<Array<unknown>>
      readonly execute: (sql: string) => Effect.Effect<void>
    }
    
    class Database extends Context.Tag("Database")<Database, DatabaseService>() {}
    
    // ❌ Bad: Inline type
    class Database extends Context.Tag("Database")<
      Database,
      { query: (sql: string) => Effect.Effect<Array<unknown>> }
    >() {}
    
    4
    Use Unique Keys
    5
    Ensure tag keys are unique across your application:
    6
    // ✅ Good: Unique keys
    class Database extends Context.Tag("MyApp/Database")<...>() {}
    class Logger extends Context.Tag("MyApp/Logger")<...>() {}
    
    // ❌ Bad: Generic keys that might collide
    class Database extends Context.Tag("db")<...>() {}
    
    7
    Keep Services Small
    8
    Follow the single responsibility principle:
    9
    // ✅ Good: Focused services
    class UserRepository extends Context.Tag("UserRepository")<
      UserRepository,
      {
        findById: (id: string) => Effect.Effect<User, NotFoundError>
        save: (user: User) => Effect.Effect<void>
      }
    >() {}
    
    class EmailService extends Context.Tag("EmailService")<
      EmailService,
      { send: (to: string, body: string) => Effect.Effect<void> }
    >() {}
    
    // ❌ Bad: God service
    class AppService extends Context.Tag("AppService")<
      AppService,
      {
        findUser: ...
        saveUser: ...
        sendEmail: ...
        logMessage: ...
        // etc.
      }
    >() {}
    
    10
    Use Layers for Complex Dependencies
    11
    When services have dependencies, use Layers:
    12
    import { Layer, Effect, Context } from "effect"
    
    const DatabaseLive = Layer.effect(
      Database,
      Effect.gen(function* () {
        const config = yield* Config
        const logger = yield* Logger
        // Use config and logger to create database
        return createDatabaseImpl(config, logger)
      })
    )
    

    Build docs developers (and LLMs) love