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; 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()}`); } Three habits in there:
- Conditional header. The spread adds
Authorizationonly when there's a token. - A timeout.
fetchhas none by default.AbortSignal.timeout(5000)aborts after five seconds with aTimeoutError. -
res.okis your job.fetchonly 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; 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"); 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
awaitjust works, and Auto Log showsrepo.full_name, the star count and both headers beside their lines without a singleconsole.log. - A
Responseand itsHeadersare shown in their own form when logged, soreson 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
