Schema validation for HTTP requests

When making a request with HttpClient we can also validate the response schema using @effect/schema.

In a separate schema.ts file we define the schema for a post using Schema.Class:

src/services/schema.ts
import { Schema } from "@effect/schema";

export class Post extends Schema.Class<Post>("Post")({
  userId: Schema.Number,
  id: Schema.Number,
  title: Schema.String,
  body: Schema.String,
}) {}

The schema is based on the response from the jsonplaceholder API:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

Since getPosts returns an array of posts, it's convenient to define a schema for an array of posts directly inside the Post class:

export class Post extends Schema.Class<Post>("Post")({
  userId: Schema.Number,
  id: Schema.Number,
  title: Schema.String,
  body: Schema.String,
}) {
  static readonly Array = Schema.Array(this);
}

We then validate the response type using HttpClientResponse.schemaBodyJson:

  • Access the response body using Effect.flatMap
  • Apply the schema to validate the response with HttpClientResponse.schemaBodyJson
src/services/Api.ts
const make = 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.flatMap(HttpClientResponse.schemaBodyJson(Post.Array)),
        Effect.scoped
      ),
    getPostById: (id: string) =>
      client
        .get(`/posts/${id}`)
        .pipe(
          Effect.flatMap(HttpClientResponse.schemaBodyJson(Post)),
          Effect.scoped
        ),
  } as const;
});

With this both the getPosts and getPostById methods are now type-safe:

  • getPosts returns Effect<readonly Post[], HttpClientError | ParseError>
  • getPostById returns Effect<Post, HttpClientError | ParseError>

ParseError is the error added by schemaBodyJson when the response doesn't match the schema.