What's next for api-client, and what I'd do differently
Twenty posts, one package, every feature I set out to explain in depth. This last one is different from the rest — less “here’s how a feature works,” more an honest look at where this is headed and what I’d change if I started over.
What’s actually on the roadmap
GraphQL support. Everything covered in this series assumes REST — path params, query strings, JSON bodies. A GraphQL client needs a genuinely different request shape, though a lot of the pipeline underneath — caching, dedup, retries, the auth layer — carries over conceptually almost unchanged. This is the largest single addition I’m planning, and also the one I’m least far along on, because I want the caching story for GraphQL (where “the same query” is a much fuzzier concept than “the same URL”) to be right before shipping it, not just functional.
Svelte support for the query integration. Part 13 covered React, Vue, and Solid. Svelte’s reactivity model is different enough from all three that it’s not a quick port — it needs its own real implementation, not a find-and-replace of the Vue version.
A visual devtools panel. Right now, debugging what the pipeline is doing — what’s cached, what’s deduped, what’s mid-retry — means listening to the events from Part 10 and logging them yourself. A browser devtools extension that shows this visually, live, is on the list. Honestly, writing this series and having to explain the pipeline stage-by-stage in diagrams made me want this feature more, not less — if it’s worth a diagram in a blog post, it’s worth being visible while you’re actually developing.
Better first-class support for file uploads and streaming responses. Currently possible but not particularly ergonomic — multipart bodies and streamed responses (like SSE) work, but they don’t get the same config-layering, retry, or caching treatment the rest of the pipeline gets, because streaming and “retry the whole thing on failure” don’t mix cleanly by default.
What I’d do differently if I started over
Real talk, no marketing spin: a few decisions I’d reconsider, in hindsight.
The safeMode naming. In Part 9 I covered when I actually reach for it
— mostly scripts and bulk jobs, rarely typical UI code. Looking back, I
think the name oversells it a little; “safe” implies the throwing default is
somehow unsafe, when really it’s just a different calling convention suited
to a different context. If I were naming it today I’d probably call it
something like resultMode, more descriptive of the actual mechanism and
less loaded with an implication I didn’t intend.
Codegen and runtime validation shipping as separate concerns from day one. They ended up deeply related — Part 12 covered how drift-checking exists specifically because codegen alone can’t catch a backend that’s drifted since generation. In hindsight, I’d have designed the two together from the start instead of building codegen first and bolting validation on once I ran into the drift problem for real. It works fine as it is, but the API surface between them has a couple of rough edges that a unified design from day one probably would have avoided.
Underestimating how much documentation the RPC bridge’s security model would need. Parts 14 and 15 took longer to write than any other two posts in this series combined, and that’s roughly proportional to how much longer that feature took to get right compared to everything else in the package. If anything, I’d have started documenting the threat model while building it, not after — writing the CSRF-per-transport reasoning down as a design doc first would probably have caught a couple of the edge cases sooner than I actually did.
The thing that hasn’t changed since post one
Every feature in this series — rotation-safe auth, cross-user-safe caching, coalesced refresh calls, deny-by-default RPC exposure — is a boring, defensive choice closing one specific, previously-real bug. None of it is clever for the sake of being clever. That was true of the JWT rotation work in MERN Notes that this whole approach grew out of, and it’s true of every post in this series. Auth and API layers are areas where “boring and correct” beats “impressive” every time, because the failure mode when you get it wrong isn’t a bug ticket — it’s a user’s data going somewhere it shouldn’t.
If you’ve read this whole series, you now know this package about as well as I do — every design decision, every bug that led to it, every place I’d reconsider. If you find something I got wrong, or a case this doesn’t handle well, that’s genuinely useful information, not something to spare my feelings about. That’s most of why I write these in this much detail in the first place.
- developerehsan