The problem with data validation

The API request works, great!

But! Are we really sure we are getting back a valid Pokémon?

Even our plain typescript implementation returns unknown (return type of response.json()):

const main = async (): Promise<unknown> => {
  const response = await fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");
  if (!response.ok) {
    throw new Error("Response not okay");
  }

  const json = await response.json();
  return json;
};

How do we make sure that we got a Pokémon?

First, let's define a simple interface for a Pokémon (based on the response from PokéApi):

interface Pokemon {
  id: number;
  order: number;
  name: string;
  height: number;
  weight: number;
}

Now, we need to type the API response from unknown to Pokemon.

This is complex in plain typescript. So much so that most people often default to type casting using as:

interface Pokemon {
  id: number;
  order: number;
  name: string;
  height: number;
  weight: number;
}

const main = async (): Promise<Pokemon> => {
  const response = await fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");
  if (!response.ok) {
    throw new Error("Response not okay");
  }

  const json = await response.json();
  return json as Pokemon;
};

as in typescript is a way to bypass type validation. You are saying: "I know better than you typescript, so listen to me an type this as Pokemon"

as exposes your codebase to runtime bugs, use it with extreme care (ideally never!)

In reality we are not sure that the response is indeed a valid Pokemon. We need to introduce runtime validation to check this.

This is where @effect/schema comes to our rescue!