Creating User Service

Avatar of Hemanta SundarayHemanta Sundaray

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

services/user-service.ts
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from "@effect/platform";
import { Effect } from "effect";
import {
ConfigError,
GetUsersError,
GetUsersParseError,
GetUsersRequestError,
GetUsersResponseError,
} from "@/errors";
import { UsersSchema, type UsersResponse } from "@/schema/user-schema";
import { apiBaseUrlConfig } from "@/lib/config";
import { USERS_PER_PAGE } from "@/lib/constants";
export class UserService extends Effect.Service<UserService>()(
"app/UserService",
{
effect: Effect.gen(function* () {
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.filterStatusOk,
);
const apiBaseUrl = yield* apiBaseUrlConfig.pipe(
Effect.catchTag("ConfigError", (error) =>
Effect.fail(
new ConfigError({
message: "API base URL is not configured.",
cause: error,
}),
),
),
);
// ============ Get Users ============
function getUsers(): Effect.Effect<UsersResponse, GetUsersError> {
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.catchTags({
RequestError: (requestError) =>
Effect.fail(
new GetUsersRequestError({
message: "Failed to get users",
cause: requestError,
}),
),
ResponseError: (responseError) =>
Effect.fail(
new GetUsersResponseError({
message: `Failed to get users: status ${responseError.response.status}`,
cause: responseError,
}),
),
ParseError: (parseError) => {
return Effect.fail(
new GetUsersParseError({
message: "Failed to parse getUsers response",
cause: parseError,
}),
);
},
}),
);
}
return { getUsers };
}),
dependencies: [FetchHttpClient.layer],
accessors: true,
},
) {}

We define UserService using Effect.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 ResponseError, so we don’t have to manually check status codes to distinguish between successful and failed responses.

Note

By default, HttpClient does not treat non-2xx status codes as errors.

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 ParseError. 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. When we use UserService.Default, this dependency is automatically included.

Notice that we specify accessors: true. This generates a static getUsers method on the UserService class, allowing us to write UserService.getUsers() instead of first extracting the service and then calling the method.

Sign in to save progress

Stay in the loop

Get notified when new chapters are added and when this course is complete.