⚡️ Could a 20-line JS function replace your bulky SDK?
Share
Bookmark
A tiny, focused wrapper can often cover 80% of what an SDK does — without the bloat. Let’s see when a compact JS function is smarter than dragging a whole SDK into your bundle.
✅ What is a 20-line JS function that replaces an SDK?
• A small, hand-rolled wrapper around fetch (or fetch + node polyfill) that handles base URLs, auth headers, JSON parsing, retries, and a couple of convenience methods.
• Not a full-featured SDK — just the minimal surface your app needs (GET/POST helpers, error normalization, optional retry/backoff).
• Highly opinionated and tiny: you control behavior, deps, bundle size, and upgrades.
🎯 Why Developers Should Care
Bundle size wins — Removing a bulky SDK can shave KBs off your client build and improve TTFB and Lighthouse scores.
Debuggability — Small code means you know exactly what runs on each request; no hidden side-effects or magic.
Faster onboarding — New devs read 20 lines faster than 2000 lines of SDK docs.
🧠 How to Use a 20-line JS function – Practical Workflow
Identify the 20% of SDK features you actually use (endpoints, auth, retries).
Write a minimal wrapper with a clear API (e.g.,
get,post,raw). Keep side-effects out.Replace SDK calls incrementally — route one feature at a time to the wrapper and run tests.
Add small helpers only when needed (e.g., retry/backoff or content-negotiation).
Keep the wrapper versioned in your repo (or publish as a tiny internal package) and add CI tests that verify parity with the SDK for critical endpoints.
// 20-line JS wrapper (ready-to-drop-in)
const api = (base, key) => {
const call = async (path, opts = {}) => {
const url = base + path;
const headers = { 'Content-Type': 'application/json', ...(opts.headers||{}), ...(key?{Authorization:`Bearer ${key}`}:{}) };
const body = opts.body ? JSON.stringify(opts.body) : undefined;
for (let i=0;i<3;i++){
try{
const res = await fetch(url, {...opts, headers, body});
if (!res.ok) throw {status: res.status, text: await res.text()};
const text = await res.text();
try { return JSON.parse(text); } catch(e){ return text; }
}catch(e){
if (i===2) throw e;
await new Promise(r=>setTimeout(r, 200*(i+1)));
}
}
};
return { get: (p)=>call(p,{method:'GET'}), post: (p,b)=>call(p,{method:'POST', body:b}), raw: call };
};
export default api;
✍️ Prompts to Try
• Write a 20-line JS wrapper around fetch that supports baseURL, bearer auth, JSON parse, and 3 retry attempts.
• Compare this 20-line wrapper to [SDK_NAME]: pros/cons for bundle size, features, and security.
• Refactor the wrapper to add exponential backoff and a 2s request timeout.
• Convert the wrapper to TypeScript with minimal types for request/response.
• Create unit tests for get/post methods that mock fetch and assert retry behavior.
⚠️ Things to Watch Out For
• Security: don't hard-code secrets; use env variables and rotate tokens server-side.
• Feature gaps: SDKs bundle edge-case fixes (pagination helpers, telemetry, websockets) you might miss.
• Maintenance: your tiny wrapper is your responsibility — keep tests and docs.
• Runtime differences: fetch behaves slightly different in browsers vs Node (polyfills, streaming).
• Rate limits & retries: naive retries can amplify throttling — respect Retry-After and status codes.
🚀 Best Use-Cases
• Small apps or micro-frontends where bundle size matters.
• Prototypes and MVPs that need to move fast without SDK lock-in.
• Serverless functions where cold-start and package size are critical.
• When you only need a handful of endpoints and full SDK is overkill.
• Learning/inspecting how an API works before committing to an SDK.
🔍 Final Thoughts
A tiny wrapper gives you control, speed, and clarity — but it’s not a silver bullet. Start lightweight and only grow when needed. Which endpoint do you want to convert to a 20-line wrapper first?