Api request with platform HttpClient

We can now implement the actual API requests using @effect/platform.

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 define the effect inside Effect.Service:

src/services/Api.ts
import { Effect } from "effect";

export class Api extends Effect.Service<Api>()("Api", {
  effect: // ...
}) {}

Inside effect we extract the base HttpClient from @effect/platform:

src/services/Api.ts
import { HttpClient } from "@effect/platform";
import { Effect } from "effect";

export class Api extends Effect.Service<Api>()("Api", {
  effect: Effect.gen(function* () {
    const baseClient = yield* HttpClient.HttpClient;
    
    // ...
  }),
}) {}

We then 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)
src/services/Api.ts
import { HttpClient, HttpClientRequest } from "@effect/platform";
import { Effect, flow } from "effect";

export class Api extends Effect.Service<Api>()("Api", {
  effect: 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:

src/services/Api.ts
import { HttpClient, HttpClientRequest } from "@effect/platform";
import { Effect, flow } from "effect";

export class Api extends Effect.Service<Api>()("Api", {
  effect: 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}`)
    };
  }),
}) {}

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.

src/services/Api.ts
import { HttpClient, HttpClientRequest } from "@effect/platform";
import { Effect, flow } from "effect";

export class Api extends Effect.Service<Api>()("Api", {
  effect: 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),
    };
  }),
}) {}

This is all we need for a working API service.