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
:
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-Type
header 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.get
returnsEffect<HttpClientResponse, HttpClientError, Scope>
:
HttpClientResponse
is the response from the APIHttpClientError
contains any error returned by the APIScope
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.
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.