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
}),
)
})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
}),
)
})