DEV Community

Cover image for What Building Open Source Fetch Tools Taught Me - And What They Make Me Wanna Forget
Gabor Koos
Gabor Koos

Posted on

What Building Open Source Fetch Tools Taught Me - And What They Make Me Wanna Forget

This is my Hacktoberfest Maintainer Spotlight. If you're looking for a project to hack on this October, check out the fetch-kit org.

I've been building fetch wrappers for production since fetch came out (2015?), and it's always the same problems: timeouts, retries, avoiding retry storms. Over the years, new patterns emerged: exponential backoff, jittering, circuit breakers. But one thing hasn't changed: what worked in dev/stage/QA could become a liability in production.

After years of reinventing the wheel, two months ago I decided to do it one last time - this is ffetch. (The name comes from a dear colleague, Mr. Ffrench, with whom we first faced a retry storm in production.)

Key features of ffetch:

  • Configurable retries and backoff strategies
  • Optional open-close circuit breaker
  • Timeout management
  • Lifecycle hooks for logging, metrics, or custom behavior
  • TypeScript-first design with Node, Deno, Bun, and browser support

Setting it up is easy enough:

import createClient from '@fetchkit/ffetch'

// Create a client with timeout and retries
const api = createClient({
  timeout: 5000,
  retries: 3,
  retryDelay: ({ attempt }) => 2 ** attempt * 100 + Math.random() * 100,
})

// Make requests
const response = await api('https://api.example.com/users')
const data = await response.json()
Enter fullscreen mode Exit fullscreen mode

Once an early version was working, I added lifecycle hooks and some extra features (if honoring the Retry-After header is extra at all). After lots of trial and error, I set up CI pipelines, tsup, npm publishing, and proper versioning with changesets. For public request, I even added shiny little badges to the README. I even set up a Discord server for support and discussions.

Another pain point was error handling. Fetch doesn't throw on HTTP errors, so I added a throwHttpErrors option to ffetch. It was tricky to get all the edge cases right: what happens if a request times out, but the server responds with a 500? Or if the response is a network error but the request got cancelled? After some iterations, I'm still sure there are cases I haven't covered yet.

The Next Challenge: Testing Correctness

Initially, I tested ffetch by spinning up Express apps with endpoints that introduced latency, failures, and other chaos. But soon I realized a general-purpose tool for this could be useful - and thus chaos-proxy was born.

Chaos-proxy is a reverse proxy that can inject chaos into requests before passing them on - or even drop them. I designed it with a middleware-like architecture, letting you register premade or custom middlewares globally or per route.

Then I had to face the limitation of Express middlewares: they can only handle the request, not the response. To fix this, I had to rewrite the whole thing in Koa, which has a more powerful middleware system. I implemented middlewares like fail, latency, CORS, rateLimit, and throttle. Throttling was the most complex, but essential to simulate slow networks.

You can configure the proxy in a yaml file like this:

target: "http://localhost:4000"
port: 5000
global:
  - latency: 100
  - failRandomly:
      rate: 0.1
      status: 503
routes:
  "GET /users/:id":
    - failRandomly:
        rate: 0.2
        status: 503
  "/users/:id/orders":
    - failNth:
        n: 3
        status: 500
Enter fullscreen mode Exit fullscreen mode

And then run it with:

npx chaos-proxy --config chaos-proxy.yaml
Enter fullscreen mode Exit fullscreen mode

Bringing Chaos to Fetch in Tests

It was then when I first heard about people using Mock Service Worker to simulate network issues, which seemed a bit far-fetched to me. It's a great library, but it's not built for this. I wanted a fetch wrapper that works like chaos-proxy, with the same routing and middleware architecture - but only for testing, no full Koa app needed.

So chaos-fetch was born:

  • Swap global fetch (Node, Deno, Bun, browsers, Edge functions) with chaos-fetch in tests
  • Inject chaos via middlewares
  • Observe if your app breaks under latency, failures, or throttling

You can use it like this:

import { createClient } from '@fetchkit/chaos-fetch';

const chaosFetch = createClient({
    global: [                                        // Global rules
        { delay: { ms: 50 } },                         // Add 50ms latency
        { failRandomly: { rate: 0.1, status: 503 } },  // 10% random failures
    ],
    routes: {
        'GET api.example.com/users/:id': [             // Specific route rules
            { failNth: { n: 3, status: 500 } },          // Fail every 3rd request with status 500
        ],
    },
});

// Use as a drop-in replacement for fetch
const res = await chaosFetch('https://api.example.com/users/123');

// Or replace global fetch
replaceGlobalFetch(chaosFetch);
fetch('https://api.example.com/users/123'); // now goes through chaosFetch
restoreGlobalFetch(); // to restore original fetch
Enter fullscreen mode Exit fullscreen mode

One tricky detail: relative URLs didn't work in JSDOM because of its globalThis.origin implementation. I had to patch it in chaos-fetch to make tests reliable.

Small Scale Chaos Testing

It also got me thinking: do developers test their apps with chaos in dev/stage/QA at all? I asked around on reddit and looks like it's not common practice. Most people use chaos testing in production on the infrastructure, but not in earlier stages. I wrote a blog post about it. Exercise to the reader: would you implement chaos testing in your dev/stage/QA environments? Why not? If yes, how?

I personally think, even if not necessarily these very tools, but the idea should be adapted more widely. It's a shame that so many apps go to production without being tested against real-world network issues.

The Big Picture

Eventually, I moved all three tools into one GitHub org and deprecated the original npm packages. It seemed like a great idea at the time. At the time of writing (3 October), the org's GitHub Sponsors page is still pending, but ffetch now has 100+ stars - and I'm still determined to keep working on it.

These projects are the culmination of years of retry storms, chaos experiments, and learning how to make fetch reliable in production while testable in dev.

How to Contribute

There aren't pre-filled issues yet - and that's by design (aka laziness). If you're curious about reliable fetch or chaos testing, or just looking for a Hacktoberfest project, here's how you can get involved across the fetch-kit org:

  • Try out ffetch, chaos-proxy, or chaos-fetch in your own projects.
  • Open issues if you hit bugs, edge cases, or want to suggest new features.
  • Share feedback and ideas in the Discord community.
  • Or just star the repos to show support - it really helps with visibility (and with my daily dopamine hits).
  • If you're up for it, submit PRs with fixes, improvements, or new features. Even small contributions are welcome!
  • I'd also say sponsor the org if you find the tools useful - but no org sponsor page yet, so your wallet is safe for now.

What do you think? Would you use a wrapper like ffetch in production? Would you add chaos testing to your dev/stage environments? Or do you think it should stay in infra/production only?

Top comments (0)