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 web browser. Chrome, Firefox, Safari, or Edge from the last few years all work.
- A text editor to write a small HTML file.
- An access token, which you generate after signing in. UseGitHub, Google or Microsoft to sign in (no password to remember), then visit
/my-account/access-tokensto create one. Tokens are tied to your account so the rows you create are yours.
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:
fetch(url, options)is the browser's built-in HTTP client. It returns a Promise.- The
Authorizationheader tells the API who you are. The format isBearer <token>with a single space. - DOM manipulation via
document.getElementByIdandcreateElementto put the result on the page.
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").
POSTreturns 201 with the new resource (including its server-assigned id).PATCHreturns 200 with the updated row.DELETEreturns 204 with no body.
// 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:
X-Pagination-Totalis the total number of rows matching your filter.X-Pagination-Pagesis how many pages exist.X-Pagination-Pageis the page you are on.X-Pagination-Limitis the page size in rows.
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
- Open DevTools while you work. Press F12 (or Cmd+Option+I on Mac), switch to the Console tab. You can paste any of the snippets in this guide directly into the console and run them on a page that already has
TOKENdefined. - Use the Network tab. Every fetch call shows up there. Click a request to see the headers it sent, the response, and the timing. This is the fastest way to debug "why did the API say 401?"
- Tokens in client code. Putting the token directly in
index.htmlis fine for a personal demo or a tutorial, but the file is visible to anyone who opens DevTools. For production apps, the safer pattern is to keep the token on your own backend and have the browser call your backend, which forwards the request to Go REST. - Test slow responses and errors. Append
?delay=1500to any GET to simulate latency, or?force_status=429to test your error path. The headersX-Simulated-Delay-MsandX-Simulated-Statusconfirm the simulation fired.