Creating User Service

Avatar of Hemanta SundarayHemanta Sundaray

In services/user-service.ts, add the following code:

services/user-service.ts
import { Effect, Layer, ServiceMap } from "effect";
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from "effect/unstable/http";
import { apiBaseUrlConfig } from "@/lib/config";
import { USERS_PER_PAGE } from "@/lib/constants";
import { mapHttpError } from "@/lib/http-error";
import { ParseError, type HttpError } from "@/errors";
import { UsersSchema, type UsersResponse } from "@/schema/user-schema";
export class UserService extends ServiceMap.Service<UserService>()(
"app/UserService",
{
make: Effect.gen(function* () {
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.filterStatusOk,
);
const apiBaseUrl = yield* apiBaseUrlConfig;
// ============ Get Users ============
function getUsers(): Effect.Effect<UsersResponse, HttpError> {
const request = HttpClientRequest.get(`${apiBaseUrl}/users`).pipe(
HttpClientRequest.setUrlParams({
_limit: USERS_PER_PAGE.toString(),
}),
);
return client.execute(request).pipe(
Effect.delay("1 second"),
Effect.flatMap((response) =>
Effect.all({
users: HttpClientResponse.schemaBodyJson(UsersSchema)(response),
usersCount: Effect.succeed(
Number(response.headers["x-total-count"]),
),
}),
),
Effect.catchTag("HttpClientError", (error) =>
Effect.fail(mapHttpError(error)),
),
Effect.catchTag("SchemaError", (error) =>
Effect.fail(
new ParseError({
message: "Received an unexpected response from the server.",
cause: error,
}),
),
),
);
}
return { getUsers };
}),
},
) {
static layer = Layer.effect(this, this.make).pipe(
Layer.provide(FetchHttpClient.layer),
);
}

We define UserService using ServiceMap.Service. Let’s break down the key parts.

We obtain an HttpClient instance and pipe it through filterStatusOk. This ensures that any non-2xx response automatically fails the Effect with a HttpClientError with reason set to a StatusCodeError, so we don’t have to manually check status codes to distinguish between successful and failed responses.

The getUsers function constructs a GET request with the _limit query parameter and executes it. It returns an object with two properties: users and usersCount. The users property contains the response body, parsed and validated against UsersSchema. If the response doesn’t match the schema, the Effect fails with a SchemaError. The usersCount property contains the total number of users, read from the x-total-count response header.

Notice the Effect.delay("1 second") line. Since our API reads from a local db.json file, responses are nearly instant. The 1-second delay simulates realistic network latency, giving us a chance to observe loading states.

Note

The _limit query parameter and the x-total-count response header are specific to json-server.

The _limit parameter controls how many items are returned per request. The x-total-count header contains the total number of users, which we’ll use to implement pagination later.

The UserService depends on HttpClient, which we provide via FetchHttpClient.layer in the static layer property.

Sign in to save progress

Stay in the loop

Get notified when new Effect Atom related content is published.