Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tim-smart/effect-atom/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The AtomHttpApi module provides seamless integration between Effect Atom and @effect/platform’s HttpApi, enabling type-safe HTTP API calls with built-in state management and reactivity.

Creating an HTTP API client

Use AtomHttpApi.Tag to create a special Context.Tag that builds an HTTP API client with atom integration:
import { AtomHttpApi } from "@effect-atom/atom-react"
import {
  FetchHttpClient,
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup
} from "@effect/platform"
import { Schema } from "effect"

// Define your API
class Api extends HttpApi.make("api").add(
  HttpApiGroup.make("counter").add(
    HttpApiEndpoint.get("count", "/count")
      .addSuccess(Schema.Number)
  ).add(
    HttpApiEndpoint.post("increment", "/increment")
  )
) {}

// Create the HTTP API client tag
class CountClient extends AtomHttpApi.Tag<CountClient>()("CountClient", {
  api: Api,
  httpClient: FetchHttpClient.layer,
  baseUrl: "http://localhost:3000"
}) {}

Type signature

interface AtomHttpApiClient<Self, Id extends string, Groups extends HttpApiGroup.HttpApiGroup.Any, ApiE, E> {
  // The layer for providing the client
  readonly layer: Layer.Layer<Self, E>
  
  // Runtime for creating atoms
  readonly runtime: Atom.AtomRuntime<Self, E>
  
  // Create mutations (write operations)
  readonly mutation: <
    GroupName extends HttpApiGroup.HttpApiGroup.Name<Groups>,
    Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>,
    WithResponse extends boolean = false
  >(
    group: GroupName,
    endpoint: Name,
    options?: { readonly withResponse?: WithResponse }
  ) => Atom.AtomResultFn<Request, Success | [Success, HttpClientResponse], Error>
  
  // Create queries (read operations)
  readonly query: <
    GroupName extends HttpApiGroup.HttpApiGroup.Name<Groups>,
    Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>,
    WithResponse extends boolean = false
  >(
    group: GroupName,
    endpoint: Name,
    request: Request & {
      readonly reactivityKeys?: ReadonlyArray<unknown> | ReadonlyRecord<string, ReadonlyArray<unknown>>
      readonly timeToLive?: Duration.DurationInput
    }
  ) => Atom.Atom<Result.Result<Success | [Success, HttpClientResponse], Error>>
}

Queries

Queries are read-only operations that automatically cache and manage their state:
import { Result, useAtomValue } from "@effect-atom/atom-react"

function CountDisplay() {
  const count = useAtomValue(
    CountClient.query("counter", "count", {
      // Optional: Add reactivity keys for cache invalidation
      reactivityKeys: ["count"],
      // Optional: Set time-to-live for caching
      timeToLive: "5 minutes"
    })
  )

  return (
    <div>
      Count: {Result.getOrElse(count, () => 0)}
    </div>
  )
}

Query with request parameters

Pass path parameters, URL params, headers, and payload:
class Api extends HttpApi.make("api").add(
  HttpApiGroup.make("users").add(
    HttpApiEndpoint.get("getUser", "/users/:id")
      .addSuccess(Schema.Struct({
        id: Schema.String,
        name: Schema.String
      }))
  )
) {}

class UserClient extends AtomHttpApi.Tag<UserClient>()("UserClient", {
  api: Api,
  httpClient: FetchHttpClient.layer,
  baseUrl: "http://localhost:3000"
}) {}

// Use with path parameters
const userAtom = UserClient.query("users", "getUser", {
  path: { id: "123" },
  reactivityKeys: ["user", "123"]
})

Query options

  • reactivityKeys: Array of keys used for cache invalidation when mutations occur
  • timeToLive: Duration to cache the query result. Use "Infinity" to cache forever
  • headers: Custom HTTP headers to include in the request
  • path: Path parameters for the endpoint
  • urlParams: Query string parameters
  • payload: Request body
  • withResponse: Include the full HTTP response along with the parsed body

Mutations

Mutations are write operations that can invalidate query caches:
import { useAtomSet } from "@effect-atom/atom-react"

function IncrementButton() {
  const increment = useAtomSet(CountClient.mutation("counter", "increment"))

  return (
    <button
      onClick={() =>
        increment({
          payload: void 0,
          // Invalidate count queries when mutation completes
          reactivityKeys: ["count"]
        })
      }
    >
      Increment
    </button>
  )
}

Getting the full response

Set withResponse: true to receive both the parsed body and the HTTP response:
const userWithResponse = CountClient.query("users", "getUser", {
  path: { id: "123" },
  withResponse: true
})

// Type: Result.Result<[User, HttpClientResponse], Error>

function UserWithHeaders() {
  const result = useAtomValue(userWithResponse)
  
  return Result.builder(result)
    .onSuccess(([user, response]) => (
      <div>
        <p>User: {user.name}</p>
        <p>ETag: {response.headers["etag"]}</p>
      </div>
    ))
    .render()
}

Custom atoms

You can create custom atoms using the CountClient.runtime:
import { Effect } from "effect"

const incrementAtom = CountClient.runtime.fn(
  Effect.fnUntraced(function*() {
    const client = yield* CountClient
    yield* client.counter.increment()
  })
)

const countAtom = CountClient.runtime.atom(
  Effect.gen(function*() {
    const client = yield* CountClient
    return yield* client.counter.count()
  })
)

Using in Effect services

Integrate the HTTP API client directly into your Effect services:
class MyService extends Effect.Service<MyService>()("MyService", {
  dependencies: [CountClient.layer],
  scoped: Effect.gen(function*() {
    const client = yield* CountClient
    
    const increment = () => client.counter.increment()
    const getCount = () => client.counter.count()
    
    return { increment, getCount } as const
  })
}) {}

Reactivity keys

Reactivity keys enable automatic cache invalidation:
1
Define keys on queries
2
const userAtom = CountClient.query("users", "getUser", {
  path: { id: "1" },
  reactivityKeys: ["user", "1"]
})
3
Invalidate from mutations
4
const updateUser = CountClient.mutation("users", "updateUser")

setUpdateUser({
  path: { id: "1" },
  payload: { name: "Alice" },
  reactivityKeys: ["user", "1"]
})
5
Automatic cache updates
6
When the mutation completes, all queries with matching reactivity keys are automatically refreshed.

Advanced patterns

Parameterized queries

Use Atom.family to create parameterized query atoms:
const userAtom = Atom.family((id: string) =>
  UserClient.query("users", "getUser", {
    path: { id },
    reactivityKeys: ["user", id]
  })
)

// Usage
function UserProfile({ id }: { id: string }) {
  const user = useAtomValue(userAtom(id))
  // ...
}

Client transformation

Customize the HTTP client before requests:
class AuthClient extends AtomHttpApi.Tag<AuthClient>()("AuthClient", {
  api: Api,
  httpClient: FetchHttpClient.layer,
  baseUrl: "http://localhost:3000",
  transformClient: (client) =>
    client.pipe(
      HttpClient.mapRequest(
        HttpClientRequest.bearerToken("my-auth-token")
      )
    )
}) {}

Response transformation

Transform responses before they reach atoms:
class LoggingClient extends AtomHttpApi.Tag<LoggingClient>()("LoggingClient", {
  api: Api,
  httpClient: FetchHttpClient.layer,
  baseUrl: "http://localhost:3000",
  transformResponse: (effect) =>
    effect.pipe(
      Effect.tap((response) => Effect.log("Response:", response))
    )
}) {}

Error handling

All HTTP calls return a Result type that captures success, failure, and loading states:
function UserProfile({ id }: { id: string }) {
  const user = useAtomValue(
    UserClient.query("users", "getUser", {
      path: { id }
    })
  )
  
  return Result.builder(user)
    .onInitial(() => <div>Loading...</div>)
    .onFailure((cause) => {
      if (cause._tag === "HttpClientError") {
        return <div>HTTP Error: {cause.status}</div>
      }
      return <div>Error: {Cause.pretty(cause)}</div>
    })
    .onSuccess((user) => (
      <div>
        <h1>{user.name}</h1>
        <p>{user.email}</p>
      </div>
    ))
    .render()
}

Comparison with AtomRpc

Use AtomHttpApi for REST APIs and traditional HTTP endpoints. Use AtomRpc for RPC-style APIs with custom protocols (WebSocket, SSE, etc.).
FeatureAtomHttpApiAtomRpc
ProtocolHTTP/RESTWebSocket, SSE, HTTP
API styleRESTful endpointsRPC methods
StreamingNot supportedBuilt-in support
Type safetyFullFull
Best forTraditional REST APIsReal-time, bidirectional APIs

Build docs developers (and LLMs) love