DEV Community

Cover image for Prototype an API call before you write the client
JSLab
JSLab

Posted on

Prototype an API call before you write the client

Before writing a client for an API, I want answers: what does the response actually look like, what happens without a token, what are the limits? A scratch file gets those answers faster than reading the docs twice.

Our test subject: the GitHub REST API, which works without a token (with a low rate limit) and with one.

Step 1: the token comes from the environment

Never paste a token into code, even scratch code. It ends up in a screenshot, a gist or a commit.

const token = process.env.GITHUB_TOKEN;
Enter fullscreen mode Exit fullscreen mode

In Node and Bun, process.env holds the environment the process started with. undefined here is a valid state, and the code should handle it.

Step 2: a request that fails properly

const res = await fetch("https://api.github.com/repos/oven-sh/bun", {
  headers: {
    Accept: "application/vnd.github+json",
    ...(token ? { Authorization: `Bearer ${token}` } : {}),
  },
  signal: AbortSignal.timeout(5000),
});

if (!res.ok) {
  throw new Error(`GitHub ${res.status}: ${await res.text()}`);
}
Enter fullscreen mode Exit fullscreen mode

Three habits in there:

  • Conditional header. The spread adds Authorization only when there's a token.
  • A timeout. fetch has none by default. AbortSignal.timeout(5000) aborts after five seconds with a TimeoutError.
  • res.ok is your job. fetch only rejects on network failure. A 404 or 403 resolves happily, so throw on it yourself, with the body: GitHub's error messages are good.

Step 3: type what you actually got

type Repo = {
  full_name: string;
  stargazers_count: number;
  open_issues_count: number;
  pushed_at: string;
};

const repo = (await res.json()) as Repo;
repo.full_name;
repo.stargazers_count;
Enter fullscreen mode Exit fullscreen mode

Write the type from the real response, not from memory. And remember as is a promise to the compiler, not a check. For anything beyond a prototype, validate.

Step 4: read the limits

res.headers.get("x-ratelimit-limit");
res.headers.get("x-ratelimit-remaining");
Enter fullscreen mode Exit fullscreen mode

Run it without a token and with one, and compare. That's the number your client's retry logic has to respect.

In a scratchpad

I do this in JSLab, a JavaScript & TypeScript scratchpad for macOS, in a Bun tab:

  • The token goes in Tools → Environment Variables…. Values are masked on screen, they're saved in a file only your user account can read, and every run sees them in process.env. Or point the tab at your project's folder with Set Working Directory… and JSLab loads that folder's .env.
  • Top-level await just works, and Auto Log shows repo.full_name, the star count and both headers beside their lines without a single console.log.
  • A Response and its Headers are shown in their own form when logged, so res on its own line is worth a look too.

Need to hit an API that doesn't send CORS headers, from code that also touches the DOM? The Browser & Node APIs runtime has a fetch with no CORS restriction. The plain Browser runtime keeps real CORS, so you can check what a website would see.

JSLab launches Nov 15. Environment variables: https://js-lab.app/docs/environment-variables

Top comments (0)