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.
HttpClientis a generic service to perform HTTP requests. It defines shared configurations for all requests.
Inside Api.ts we define the effect inside Effect.Service:
import { Effect } from "effect";
export class Api extends Effect.Service<Api>()("Api", {
effect: // ...
}) {}Inside effect we extract the base HttpClient from @effect/platform:
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:
- Prepend the jsonplaceholder API url (
.prependUrl) - Add a
Content-Typeheader withapplication/json(.acceptJson)
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:
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.getreturnsEffect<HttpClientResponse, HttpClientError, Scope>:
HttpClientResponseis the response from the APIHttpClientErrorcontains any error returned by the APIScopeallows 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.scopedas close to the client call as possible, in this case immediately after consuming the response.
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.
