Verbose Endpoint Implementation Using Effect in TypeScript

Im going hatelove while learning effec. i tried to build one api of mine the effect way, but it got quite verbose, is this how im expected to write endpoints nowadays ?

import { Config, Console, Effect, Either, Redacted, Schema } from "effect"
import { FetchHttpClient, HttpClient } from "@effect/platform"
import { expectedResponse } from "./get-place-info.schema"
import { redis } from "@/services/redis/client"

export const getPlaceInfo = Effect.fn("getPlaceInfo")(function* (
  business_id: string,
) {
  return yield* Effect.gen(function* () {
    const value = Either.match(
      yield* Effect.either(
        Effect.tryPromise(() => redis.get(`placeInfo:${business_id}`)),
      ),
      {
        onLeft: (error) => {
          Console.error("Redis error when getting place info", error)
          console.info(`Recovering by returning null: ${business_id}`)
          return null
        },
        onRight: (val) => val,
      },
    )

    if (value) {
      const parsed = JSON.parse(value)
      const result = Either.match(
        yield* Effect.either(Schema.decodeUnknown(expectedResponse)(parsed)),
        {
          onLeft: (error) => {
            Console.error(
              "Schema validation error for cached place info",
              error,
            )
            console.info(
              `Recovering by returning null with ts : ${business_id}`,
            )
            return null
          },
          onRight: (val) => val,
        },
      )

      if (result?.data[0]) {
        return result?.data[0]
      }
    }

    const client = yield* HttpClient.HttpClient

    const response = Either.match(
      yield* Effect.either(
        client.get("https://maps-data.p.rapidapi.com/place.php", {
          urlParams: {
            business_id,
          },
          headers: {
            "x-rapidapi-host": "maps-data.p.rapidapi.com",
            //ToDo: This should be parsed at build time, but this works only with an effect-ts runtime
            "x-rapidapi-key": Redacted.value(
              yield* Config.redacted("RAPIDAPI_KEY"),
            ),
          },
        }),
      ),
      {
        onLeft: (error) => {
          Console.error("HTTP error when fetching place info", error)
          throw new Error("Try again later")
        },
        onRight: (val) => val,
      },
    )

    const json = Either.match(yield* Effect.either(response.json), {
      onLeft: (error) => {
        Console.error("Error parsing JSON from place info response", error)
        throw new Error("Try again later")
      },
      onRight: (val) => val,
    })

    Either.match(
      yield* Effect.either(
        Effect.tryPromise(() =>
          redis.setEx(`placeInfo:${business_id}`, 86400, JSON.stringify(json)),
        ),
      ),
      {
        onLeft: (error) => {
          Console.error("Redis error when setting place info", error)
        },
        onRight: () => {},
      },
    )

    const result = Either.match(
      yield* Effect.either(Schema.decodeUnknown(expectedResponse)(json)),
      {
        onLeft: (error) => {
          Console.error("Schema validation error for place info", error)
          return "Try again later"
        },
        onRight: (val) => val,
      },
    )
    if (typeof result === "string") {
      throw new Error(result)
    }

    if (!result?.data?.[0]) return null

    return result.data[0]
  })
    .pipe(Effect.provide(FetchHttpClient.layer))
    .pipe(
      Effect.catchAll((error) => {
        Console.error("Error in getPlaceInfo:", error)
        throw error
      }),
    )
})
Was this page helpful?