Api request with platform HttpClient

We can now implement the actual API requests using @effect/platform (and @effect/schema).

With @effect/platform we define a HttpClient that we use to make requests to the jsonplaceholder API.

HttpClient is a generic service to perform HTTP requests. It defines shared configurations for all requests.

Inside Api.ts we create a make function that initializes the base HttpClient. We then use make to type the contents of the Api service by extracting its success type with Effect.Effect.Success<typeof make>:

src/services/Api.ts
const make = Effect.gen(function* () {
  const baseClient = yield* HttpClient.HttpClient;
  /// ...
});

export class Api extends Context.Tag("Api")<
  Api,
  Effect.Effect.Success<typeof make>
>() {}

Inside make we extend baseClient to add some configurations to all requests:

  1. Prepend the jsonplaceholder API url (.prependUrl)
  2. Add a Content-Type header with application/json (.acceptJson)
const make = Effect.gen(function* () {
  const baseClient = yield* HttpClient.HttpClient;
  const client = baseClient.pipe(
    HttpClient.mapRequest(
      flow(
        HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"),
        HttpClientRequest.acceptJson
      )
    )
  );

  /// ...
});

Using client we can now implement the getPosts and getPostById methods:

const make = Effect.gen(function* () {
  const baseClient = yield* HttpClient.HttpClient;
  const client = baseClient.pipe(
    HttpClient.mapRequest(
      flow(
        HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"),
        HttpClientRequest.acceptJson
      )
    )
  );

  return {
    getPosts: client.get("/posts"),
    getPostById: (id: string) => client.get(`/posts/${id}`),
  } as const;
});

client.get returns Effect<HttpClientResponse, HttpClientError, Scope>:

  • HttpClientResponse is the response from the API
  • HttpClientError contains any error returned by the API
  • Scope allows you to control the lifetime of the HTTP request, aborting it once the scope is closed

Since both requests require a Scope, we can use Effect.scoped to ensure that their finalizers are run as soon as the request completes execution:

It's common to move Effect.scoped as close to the client call as possible, in this case immediately after consuming the response.

const make = Effect.gen(function* () {
  const baseClient = yield* HttpClient.HttpClient;
  const client = baseClient.pipe(
    HttpClient.mapRequest(
      flow(
        HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"),
        HttpClientRequest.acceptJson
      )
    )
  );

  return {
    getPosts: client
      .get("/posts")
      .pipe(Effect.scoped),
    getPostById: (id: string) =>
      client
        .get(`/posts/${id}`)
        .pipe(Effect.scoped),
  } as const;
});

This is all we need for a working API service.