TypeFetched/manual/chapter-2-enter-typedfetch.md
Casey Collier b85b9a63e2 Initial commit: TypedFetch - Zero-dependency, type-safe HTTP client
Features:
- Zero configuration, just works out of the box
- Runtime type inference and validation
- Built-in caching with W-TinyLFU algorithm
- Automatic retries with exponential backoff
- Circuit breaker for resilience
- Request deduplication
- Offline support with queue
- OpenAPI schema discovery
- Full TypeScript support with type descriptors
- Modular architecture
- Configurable for advanced use cases

Built with bun, ready for npm publishing
2025-07-20 12:35:43 -04:00

13 KiB

Chapter 2: Enter TypedFetch - Your API Superpower

"The difference between a tool and a superpower is how it makes you feel when you use it."


The Moment Everything Changes

Remember Sarah from Chapter 1? She'd figured out APIs, but her code was getting messy. Error handling was a nightmare. Every API call looked like this:

fetch('https://api.weather.com/forecast')
  .then(response => {
    if (!response.ok) {
      if (response.status === 404) {
        throw new Error('City not found')
      } else if (response.status === 401) {
        throw new Error('Invalid API key')
      } else {
        throw new Error('Something went wrong')
      }
    }
    return response.json()
  })
  .then(data => {
    // Finally! The actual data
    updateWeatherDisplay(data)
  })
  .catch(error => {
    console.error('Error:', error)
    showErrorMessage(error.message)
  })

15 lines of code just to make one API call. And she had dozens of these throughout her app.

Then her colleague Dave walked by. "Why are you writing all that boilerplate? Just use TypedFetch."

"TypedFetch?"

Dave smiled and rewrote her code:

import { tf } from 'typedfetch'

const { data } = await tf.get('https://api.weather.com/forecast')
updateWeatherDisplay(data)

Sarah stared. "That's it?"

"That's it."

Installing Your Superpower

Let's get TypedFetch into your project. It takes literally one command:

npm install typedfetch

Or if you prefer yarn/pnpm/bun:

yarn add typedfetch
pnpm add typedfetch
bun add typedfetch

That's it. No configuration files. No setup wizard. No initialization. It just works.

Your First TypedFetch Call

Let's rewrite that dad joke fetcher from Chapter 1:

// The old way (fetch)
fetch('https://icanhazdadjoke.com/', {
  headers: { 'Accept': 'application/json' }
})
.then(response => response.json())
.then(data => console.log(data.joke))
.catch(error => console.error('Error:', error))

// The TypedFetch way
import { tf } from 'typedfetch'

const { data } = await tf.get('https://icanhazdadjoke.com/', {
  headers: { 'Accept': 'application/json' }
})
console.log(data.joke)

Notice what's missing? All the ceremony. The .then() chains. The manual JSON parsing. The basic error handling. TypedFetch handles all of that for you.

But Wait, What About Errors?

Great question! Let's break something on purpose:

try {
  // This URL doesn't exist
  const { data } = await tf.get('https://fakesiteabcd123.com/api')
} catch (error) {
  console.log(error.message)
  console.log(error.suggestions)  // <- This is new!
}

Output:

Failed to fetch https://fakesiteabcd123.com/api: fetch failed

Suggestions:
• Check network connection
• Verify URL is correct  
• Try again in a moment

TypedFetch doesn't just tell you something went wrong - it helps you fix it. Every error comes with:

  • A clear error message
  • Suggestions for fixing it
  • Debug information when you need it

The Magic of Zero Configuration

Here's what TypedFetch configures automatically:

  1. JSON Parsing - Response automatically parsed
  2. Error Handling - Network and HTTP errors caught
  3. Content Headers - Sets 'Content-Type' for you
  4. Smart Retries - Retries failed requests intelligently
  5. Request Deduplication - Prevents duplicate simultaneous calls

Let's see this in action:

// Making multiple simultaneous calls to the same endpoint
const promise1 = tf.get('https://api.github.com/users/torvalds')
const promise2 = tf.get('https://api.github.com/users/torvalds')
const promise3 = tf.get('https://api.github.com/users/torvalds')

const [result1, result2, result3] = await Promise.all([promise1, promise2, promise3])

// TypedFetch only made ONE actual network request!
// All three promises got the same result

Let's Upgrade Weather Buddy

Remember our weather app from Chapter 1? Let's give it the TypedFetch treatment:

<!DOCTYPE html>
<html>
<head>
    <title>Weather Buddy 2.0</title>
    <script type="module">
        import { tf } from 'https://esm.sh/typedfetch'
        
        window.getWeather = async function() {
            const city = document.getElementById('cityInput').value || 'Seattle'
            const resultDiv = document.getElementById('result')
            
            try {
                // So much cleaner!
                const { data } = await tf.get(`https://wttr.in/${city}?format=j1`)
                
                resultDiv.innerHTML = `
                    <h2>Weather in ${data.nearest_area[0].areaName[0].value}</h2>
                    <p>🌡️ Temperature: ${data.current_condition[0].temp_C}°C / ${data.current_condition[0].temp_F}°F</p>
                    <p>🌤️ Condition: ${data.current_condition[0].weatherDesc[0].value}</p>
                    <p>💨 Wind: ${data.current_condition[0].windspeedKmph} km/h</p>
                    <p>💧 Humidity: ${data.current_condition[0].humidity}%</p>
                `
            } catch (error) {
                // TypedFetch gives us helpful error messages
                resultDiv.innerHTML = `
                    <h2>Oops! Something went wrong</h2>
                    <p>${error.message}</p>
                    <ul>
                        ${error.suggestions?.map(s => `<li>${s}</li>`).join('') || ''}
                    </ul>
                `
            }
        }
    </script>
</head>
<body>
    <h1>Weather Buddy 2.0 🌤️</h1>
    <input type="text" id="cityInput" placeholder="Enter city name" />
    <button onclick="getWeather()">Get Weather</button>
    <div id="result"></div>
</body>
</html>

Look at that error handling! If something goes wrong, TypedFetch tells the user exactly what happened and how to fix it.

The TypedFetch Philosophy

TypedFetch follows three core principles:

1. Batteries Included 🔋

Everything you need is built-in. No plugins to install, no middleware to configure.

// Caching? Built-in.
const user1 = await tf.get('/api/user/123') // Network call
const user2 = await tf.get('/api/user/123') // Cache hit!

// Retries? Built-in.
const data = await tf.get('/flaky-api')     // Automatically retries on failure

// Type safety? Built-in. (We'll cover this in Chapter 7)
const user = await tf.get<User>('/api/user/123')

2. Progressive Disclosure 📈

Simple things are simple. Complex things are possible.

// Simple: Just get data
const { data } = await tf.get('/api/users')

// Advanced: Full control when you need it
const { data, response } = await tf.get('/api/users', {
  headers: { 'Authorization': 'Bearer token' },
  cache: false,
  retries: 5,
  timeout: 10000
})
console.log('Status:', response.status)
console.log('Headers:', response.headers)

3. Developer Empathy ❤️

Every feature is designed to make your life easier.

// Debugging? One line.
tf.enableDebug()

// Now every request logs helpful information:
// [TypedFetch] GET https://api.example.com/users
// [TypedFetch] ✅ 200 OK (123ms)
// [TypedFetch] 📦 Response size: 2.4kb
// [TypedFetch] 💾 Cached for 5 minutes

Real-World Comparison

Let's fetch a GitHub user with both approaches:

The fetch() Way:

async function getGitHubUser(username) {
  try {
    const response = await fetch(`https://api.github.com/users/${username}`)
    
    if (!response.ok) {
      if (response.status === 404) {
        throw new Error(`User ${username} not found`)
      } else if (response.status === 403) {
        throw new Error('Rate limit exceeded. Try again later.')
      } else {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }
    }
    
    const data = await response.json()
    return data
    
  } catch (error) {
    if (error instanceof TypeError) {
      throw new Error('Network error. Check your connection.')
    }
    throw error
  }
}

The TypedFetch Way:

async function getGitHubUser(username) {
  const { data } = await tf.get(`https://api.github.com/users/${username}`)
  return data
}

Both handle errors properly. Both work with async/await. But which one would you rather write 50 times in your app?

Common Questions (With Answers!)

Q: "Is TypedFetch just a wrapper around fetch()?"
A: It's like asking if a Tesla is just a wrapper around wheels. Yes, it uses fetch() internally, but adds intelligent caching, automatic retries, error enhancement, type safety, request deduplication, and more.

Q: "What about bundle size?"
A: The entire core is ~12KB gzipped. For context, that's smaller than most images on a webpage.

Q: "Does it work in Node.js/Deno/Bun?"
A: Yes! TypedFetch works everywhere fetch() works.

Q: "What if I need the raw Response object?"
A: You got it:

const { data, response } = await tf.get('/api')
console.log(response.headers.get('content-type'))

Q: "Can I still use async/await?"
A: That's the ONLY way to use TypedFetch. No more callback hell or promise chains.

Your New Superpowers

Here's what you can now do that you couldn't before:

// 1. Automatic caching
const user = await tf.get('/api/user')  // First call: ~200ms
const cached = await tf.get('/api/user') // Second call: ~1ms

// 2. Smart errors
try {
  await tf.get('/bad-endpoint')
} catch (error) {
  console.log(error.suggestions) // Actually helpful!
}

// 3. Request deduplication
// If you accidentally call the same endpoint multiple times
const [a, b, c] = await Promise.all([
  tf.get('/api/data'),
  tf.get('/api/data'),
  tf.get('/api/data')
])
// Only ONE network request is made!

// 4. Built-in debugging
tf.enableDebug() // See everything that's happening

// 5. Zero config
// No setup, no initialization, just import and use

Practice Time! 🏋️

Exercise 1: Convert to TypedFetch

Take these fetch() calls and rewrite them using TypedFetch:

// 1. Basic GET
fetch('https://api.quotable.io/random')
  .then(r => r.json())
  .then(data => console.log(data))

// 2. POST with data
fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  body: JSON.stringify({
    title: 'My Post',
    body: 'This is the content',
    userId: 1
  }),
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(r => r.json())
.then(data => console.log(data))

Exercise 2: Error Enhancement

Make this request fail and examine the error:

try {
  // This domain doesn't exist
  await tf.get('https://this-domain-definitely-does-not-exist-123456.com/api')
} catch (error) {
  console.log('Message:', error.message)
  console.log('Type:', error.type)
  console.log('Suggestions:', error.suggestions)
  
  // Try the debug function
  if (error.debug) {
    error.debug()
  }
}

Exercise 3: Cache Detective

Prove that TypedFetch is caching:

// Time the first call
console.time('First call')
await tf.get('https://api.github.com/users/torvalds')
console.timeEnd('First call')

// Time the second call
console.time('Second call')
await tf.get('https://api.github.com/users/torvalds')
console.timeEnd('Second call')

// What's the difference?

Key Takeaways 🎯

  1. TypedFetch is a zero-config API client - Just install and use
  2. It handles all the boilerplate - JSON parsing, error handling, headers
  3. Errors are actually helpful - With suggestions and debug info
  4. Smart features work automatically - Caching, retries, deduplication
  5. Progressive disclosure - Simple by default, powerful when needed

What's Next?

You've got TypedFetch installed and you've seen its power. But we've only scratched the surface. In Chapter 3, we'll dive deep into GET requests and discover features like:

  • Query parameter magic
  • Response transformations
  • Custom headers and authentication
  • Performance optimization
  • Real-time data fetching

We'll also evolve Weather Buddy to show live updates, handle multiple cities, and add a search feature - all powered by TypedFetch's GET superpowers.

Ready to master the art of reading data from APIs? See you in Chapter 3! 🚀


Chapter Summary

  • TypedFetch is a zero-configuration API client that makes fetch() calls simple
  • Installation is one command: npm install typedfetch
  • Basic usage: const { data } = await tf.get(url)
  • Automatic features: JSON parsing, error handling, caching, retries, deduplication
  • Errors include helpful messages and suggestions
  • Works everywhere: browsers, Node.js, Deno, Bun
  • Progressive disclosure: simple things are simple, complex things are possible
  • Weather Buddy upgraded with better error handling and cleaner code

Next Chapter Preview: Deep dive into GET requests - the foundation of API communication. Learn query parameters, headers, authentication, and real-time updates.