Consuming REST APIs from JavaScript and Python

Intermediate
13 min

Consuming REST APIs from JavaScript and Python

So far you have designed and built APIs. Just as often you will be on the other side, calling someone else's API from a browser, a Node.js service or a Python script. In this lesson you will learn to make requests with the fetch API and Python's requests library, handle errors and timeouts, and package the calls into a small reusable client.

fetch in the Browser and Node.js

fetch is built into every modern browser and into Node.js 18 and later, so one API covers both. It returns a promise of a Response; you then read the body with .json(), .text() or .blob().

javascript
const params = new URLSearchParams({ genre: "sci-fi", limit: 5 }); const res = await fetch(`https://api.example.com/books?${params}`, { headers: { Accept: "application/json" }, }); if (!res.ok) { throw new Error(`Request failed with ${res.status}`); } const books = await res.json(); console.log(books.length, res.headers.get("X-Total-Count"));

Two things surprise newcomers: fetch only rejects on network failure, so a 404 or 500 resolves normally and you must check res.ok (true for 200-299); and the body can be read only once. To send data, set the method, a Content-Type header and a serialized body:

javascript
const res = await fetch("https://api.example.com/books", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ title: "Dune", author: "Frank Herbert" }), }); if (res.status === 201) console.log("Created at", res.headers.get("Location"));

Timeouts and Cancellation

fetch has no timeout by default; a hung connection can wait forever. Pass a signal option: AbortSignal.timeout(8000) creates a signal that fires after 8 seconds, and a manual AbortController lets you cancel when, for example, the user navigates away by calling controller.abort(). An aborted request rejects with an error whose name is "AbortError" (or "TimeoutError" for AbortSignal.timeout), so you can tell it apart from a real failure.

A Reusable Client

Repeating headers, JSON parsing and error checks in every call is how bugs creep in. Wrap them once:

javascript
async function api(path, { method = "GET", body, token } = {}) { const res = await fetch(BASE + path, { method, headers: { Accept: "application/json", ...(body && { "Content-Type": "application/json" }), ...(token && { Authorization: `Bearer ${token}` }), }, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(8000), }); if (res.status === 204) return null; const data = await res.json(); if (!res.ok) throw new Error(`${res.status}: ${data.error ?? res.statusText}`); return data; }

Retries belong in this layer too: retry GET on 429, 503 and network errors with backoff, honoring Retry-After. Libraries such as axios and ky add interceptors, but a plain fetch wrapper is often enough.

Python with requests

requests is the standard HTTP client for Python (pip install requests). It parses JSON, encodes query parameters and raises exceptions for HTTP errors on request:

python
import requests session = requests.Session() session.headers.update({"Accept": "application/json", "Authorization": f"Bearer {token}"}) resp = session.get("https://api.example.com/books", params={"genre": "sci-fi", "limit": 5}, timeout=8) resp.raise_for_status() # raises requests.HTTPError for 4xx and 5xx for book in resp.json(): print(book["title"]) created = session.post("https://api.example.com/books", json={"title": "Dune"}, timeout=8) print(created.status_code, created.headers.get("Location"))

A Session reuses the TCP connection and applies default headers to every call. Always pass timeout; without it a request can block forever. The json= argument serializes the body and sets Content-Type. For async code, httpx offers the same interface with async/await.

Common Mistakes

  • Forgetting res.ok in JavaScript and treating a 500 as success because the promise resolved.
  • Calling res.json() on an empty 204 response, which throws a parse error.
  • Hard-coding tokens in frontend code. Anything shipped to the browser is public.
Quick Quiz
Question 1 of 2

A `fetch` call to an endpoint that returns `404` will:

Key Takeaways

  • fetch works in browsers and Node.js 18+; check res.ok because HTTP errors do not reject.
  • Add timeouts with AbortSignal.timeout and cancel with AbortController.
  • In Python, use a requests.Session, params=, json=, timeout= and raise_for_status().
  • Centralize headers, parsing, errors and retries in one small client function.

Next lesson: CORS: Cross-Origin Requests and Preflight — understand why the browser blocks your API call and how to configure the server correctly.