What Building a Pokémon Battle Simulator Taught Me About APIs
When I started the Pokémon KO Battle project I thought the hard part would be the battle logic. I was wrong. The hard part was the API — specifically, learning to handle it gracefully when it was slow, failed, or returned data I didn't expect.
The Problem With My First Version
My first attempt fetched Pokémon data and immediately tried to use it. No loading state. No error handling. If the PokéAPI took 3 seconds (which it sometimes does on cold starts), the UI just sat there blank. If the fetch failed, the whole app broke silently. Not great.
What I Actually Learned
The fix was adding three things I had skipped in my rush to get to the fun stuff:
- A loading skeleton — placeholder shapes that appear instantly while real data loads. Users know something is happening.
- try/catch around every fetch — so a failed request shows a friendly error, not a broken screen.
- Data validation — PokéAPI returns deeply nested objects. I now always check that a property exists before I use it, rather than assuming the shape is always correct.
// Before (fragile)
const data = await fetch(url).then(r => r.json());
updateUI(data.sprites.front_default);
// After (resilient)
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
const sprite = data?.sprites?.front_default ?? '/images/fallback.png';
updateUI(sprite);
} catch (err) {
showErrorState('Could not load Pokémon. Try again.');
console.error(err);
}
The Bigger Lesson
Happy-path coding is easy. Real frontend engineering is about what happens when things go wrong. Every API call is a promise that might not be kept — and your UI should handle that gracefully.
I now apply this thinking to every project: what does the user see while waiting? What do they see if something breaks? Those two questions make the difference between a project that feels polished and one that just works in demos.