Handling an API can be tricky, for me the formula are have to be precise & type the correct data from the request.
The return from fetch are always any whatever it is you have to parse the type of the data first, before processing the data. Parsing the data, make you confident on how you handle the API.
Zod are helping us to parse the type of data, so we can be confident on what we handle, check this documentation for more information about Zod.
I used REST Harry Potter API for example, especially for list of spells. Here’s the data look like.
[
{
"spell": "Accio",
"use": "Summoning charm",
"index": 0
},
{
"spell": "Glisseo",
"use": "Turns a staircase into a slide",
"index": 1
},
...
]
As we can see, it’s an array that has objects with three keys: spell, use, and index. Then, we initiate the object key in Zod object, just like the data from the API, but this time the value are type of the data.
import { z } from "zod";
const schemaSpell = z.object({
spell: z.string(),
use: z.string(),
index: z.number(),
});
The API response are returning array, so we have to put the object inside the array.
const schemaResponse = z.array(schemaSpell.strict());
Notice that we have .strict(), the purpose of strict is to tell us that we want the schema are same as we define. It’ll return an error, if the schema are different from the API response.
Now, begin to fetch the API.
fetch("https://potterapi-fedeperin.vercel.app/en/spells", {
method: "GET",
})
.then(response => {
if (!response.ok) {
throw new Error(`Fetch error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
const parse = schemaResponse.safeParse(data); // parse the data
if (!parse.success) {
// check the data are success or not
throw new ZodError(parse.error.errors);
}
// you can handle the data here
console.log(parse.data);
})
.catch(error => {
if (error instanceof ZodError) {
console.log(error.message);
} else if (error instanceof Error) {
console.log(error.message);
}
});
So, with those code we can be confident about what’s the output of the API response.
If the schema return error, for example i change the use to used, the Zod will tell me that there’s Unrecognized key(s) in object: ‘use’ in log.
[
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
0,
"used"
],
"message": "Required"
},
{
"code": "unrecognized_keys",
"keys": [
"use"
],
"path": [
0
],
"message": "Unrecognized key(s) in object: 'use'"
},
...
]
From the error itself, we can read that use are required but the API doesn’t have use at the same time, the response are use which is the schema are not recognized it, since we change to use this is very helpful when we got a lot of data to handle.
So, here’s the full code how i handle the API with Zod as an example.
'use client'
import { useEffect, useState } from "react"
import { z, ZodError } from "zod"
const schemaSpell = z.object({
spell: z.string(),
use: z.string(),
index: z.number()
})
const schemaResponse = z.array(schemaSpell.strict())
interface SpellResponse {
spell: string,
use: string,
index: number,
}
const listSpells = (spells: SpellResponse[]) => {
return (
<>
{spells.map((spell) => (
<div key={spell.index} className="pb-2">
<p>Spell: {spell.spell} </p>
<p>Use: {spell.use}</p>
</div>
))}
</>
)
}
export default function Potter() {
const [spells, setSpells] = useState<SpellResponse[]>([])
useEffect(() => {
fetch("https://potterapi-fedeperin.vercel.app/en/spells", {
method: "GET"
})
.then((response) => {
if (!response.ok) {
throw new Error(`Fetch error! Status: ${response.status}`)
}
return response.json()
})
.then((data) => {
const parse = schemaResponse.safeParse(data)
if (!parse.success) {
throw new ZodError(parse.error.errors)
}
setSpells(parse.data)
})
.catch((error) => {
if (error instanceof ZodError) {
console.log(error.message)
} else if (error instanceof Error) {
console.log(error.message)
}
})
}, [])
return (
<>
{listSpells(spells)}
</>
)
}
the result much more like this.

Fetching an API is always tricky but at the same time it could be fun, if we get it right!