Go REST

JavaScript (Fetch API)

Browser-native fetch with async/await, bearer-token auth, error handling, and pagination.

This guide is for calling the Go REST API from a browser using plain JavaScript and the built-infetch function. Everything below runs inside an HTML page.

What you need

A complete working page

Save the file below asindex.html, replaceYOUR_ACCESS_TOKEN with your token, and open the file in your browser (double-click it, or drag it onto the browser window). You will see a list of users render on the page.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Go REST demo</title>
  </head>
  <body>
    <h1>Users</h1>
    <ul id="users"></ul>

    <script>
      const TOKEN = "YOUR_ACCESS_TOKEN"; // paste yours from /my-account/access-tokens

      fetch("https://gorest.co.in/public/v2/users", {
        headers: { Authorization: `Bearer ${TOKEN}` }
      })
        .then(async res => {
          if (!res.ok) {
            throw new Error(`HTTP ${res.status}: ${await res.text()}`);
          }
          return res.json();
        })
        .then(users => {
          const list = document.getElementById("users");
          for (const u of users) {
            const li = document.createElement("li");
            li.textContent = `${u.name} (${u.email})`;
            list.appendChild(li);
          }
        })
        .catch(err => {
          document.getElementById("users").innerHTML =
            `<li style="color:red">${err.message}</li>`;
        });
    </script>
  </body>
</html>

That is a full working program. It uses three things:

The rest of this guide breaks the code into reusable pieces and adds the patterns you will reach for in real apps.

The base URL and token

Pick a base URL once and reuse it everywhere. Save the token in a constant near the top of your script. (In production apps you should not put the token directly in client-side code; the Tips section at the bottom covers safer patterns.)

const API   = "https://gorest.co.in/public/v2";
const TOKEN = "YOUR_ACCESS_TOKEN"; // from /my-account/access-tokens

Send the bearer token on every request

All write operations and any request that needs your per-token rate-limit budget need anAuthorization header. Build it once and pass it into everyfetch call:

const headers = {
  "Authorization": `Bearer ${TOKEN}`,
  "Accept": "application/json",
  "Content-Type": "application/json"
};

The headerAccept: application/json asks for JSON back. The headerContent-Type: application/json is needed only when you send a body (POST, PATCH).

Promises and async/await

Everyfetch returns a Promise. There are two ways to read the response. Both work, butasync/await is easier to follow once you have more than one step.

// The .then() version
fetch(`${API}/users`, { headers })
  .then(res => res.json())
  .then(users => console.log(users));

// The async/await version: same thing, easier to read
async function listUsers() {
  const res   = await fetch(`${API}/users`, { headers });
  const users = await res.json();
  console.log(users);
}
listUsers();

Use whichever you find more readable. The rest of the guide usesasync/await because it scales better to multi-step flows.

List users with filters

Build the URL with theURL constructor. It URL-encodes the query parameters for you, which avoids subtle bugs with names that contain spaces or special characters.

async function listUsers({ status = "active", page = 1 } = {}) {
  const url = new URL(`${API}/users`);
  url.searchParams.set("status", status);
  url.searchParams.set("page", page);

  const res = await fetch(url, { headers });
  if (!res.ok) throw new Error(`Got ${res.status}`);
  return res.json();
}

listUsers({ status: "active" }).then(users => {
  console.log(users.length, "users on this page");
});

The supported filters on the users endpoint arename,email,gender andstatus. Strings match by substring; enums (gender, status) match exactly. The response is a plain JSON array; v2 does not wrap responses in adata envelope.

Fetch a single user

Pass the id straight in the path. Code defensively for 404. The id might not exist, or it might belong to someone else's token (which the API treats the same).

async function getUser(id) {
  const res = await fetch(`${API}/users/${id}`, { headers });
  if (res.status === 404) return null;
  if (!res.ok) throw new Error(`Got ${res.status}`);
  return res.json();
}

Create, update, delete

Writes use JSON request bodies. The four user fields the API accepts arename,email,gender ("male" or"female"), andstatus ("active" or"inactive").

// Create
async function createUser() {
  const res = await fetch(`${API}/users`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      name: "Avani Iyer",
      email: `avani-${Date.now()}@example.com`,
      gender: "female",
      status: "active"
    })
  });
  return res.json();
}

// Update (partial)
async function deactivate(id) {
  await fetch(`${API}/users/${id}`, {
    method: "PATCH",
    headers,
    body: JSON.stringify({ status: "inactive" })
  });
}

// Delete
async function removeUser(id) {
  await fetch(`${API}/users/${id}`, { method: "DELETE", headers });
}

Validation errors

When the body fails validation the API returns 422 with an array of field errors. Show those next to the form fields rather than as a generic "something went wrong":

async function tryCreate() {
  const res = await fetch(`${API}/users`, {
    method: "POST",
    headers,
    body: JSON.stringify({ name: "x" }) // missing email, gender, status
  });

  if (res.status === 422) {
    const errors = await res.json();
    // [{ field: "email", message: "can't be blank" }, ...]
    for (const e of errors) console.warn(`${e.field}: ${e.message}`);
    return;
  }

  console.log("created:", await res.json());
}

Pagination

List endpoints respond with pagination metadata in headers, not in the body:

The cleanest way to walk every page in JavaScript is an async generator. The caller treats it like a regular loop; the generator handles the paging behind the scenes.

async function* pageUsers(query = {}) {
  let page = 1;
  while (true) {
    const url = new URL(`${API}/users`);
    for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
    url.searchParams.set("page", page);

    const res = await fetch(url, { headers });
    if (!res.ok) throw new Error(`Got ${res.status}`);

    const batch = await res.json();
    if (batch.length === 0) return;
    for (const u of batch) yield u;

    const total = +res.headers.get("x-pagination-pages") || 1;
    if (page >= total) return;
    page += 1;
  }
}

// Use it like a regular for loop
(async () => {
  for await (const user of pageUsers({ status: "active" })) {
    console.log(user.email);
  }
})();

Handle rate limits

The default per-token budget is 90 requests per minute. When you hit the ceiling, the API returns 429 with anX-RateLimit-Reset header (in seconds). Wait that long and try again. Do not retry immediately, or you will spend the next request on another 429.

async function fetchWithRetry(url, opts = {}, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, opts);
    if (res.status !== 429) return res;

    const wait = (+res.headers.get("x-ratelimit-reset") || 1) * 1000;
    await new Promise(r => setTimeout(r, wait));
  }
  throw new Error("Rate limited; gave up");
}

Cancel in-flight requests

When the user types in a search box, you fire a new request on every keystroke. Without cancellation, responses can arrive out of order and the wrong result wins.AbortController is the standard fix: call.abort() on the previous request before starting the next.

let active;
document.getElementById("search").addEventListener("input", async (e) => {
  // Cancel the previous in-flight request
  active?.abort();
  active = new AbortController();

  try {
    const res = await fetch(
      `${API}/users?name=${encodeURIComponent(e.target.value)}`,
      { headers, signal: active.signal }
    );
    const matches = await res.json();
    render(matches);
  } catch (err) {
    if (err.name !== "AbortError") throw err;
  }
});

TheAbortError caught in the try/catch is the expected outcome of cancellation, not a real error, so we ignore it.

Tips

Related guides

Keep going

Back to all guides Try it in the console