JavaScript (Fetch API)
Browser-native fetch with async/await, bearer-token auth, error handling, and pagination.
This guide is for calling the GoRest API from a browser using plain JavaScript and the built-in fetch 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. Sign in with GitHub, Google or Microsoft (no password to remember), then visit /my/tokens to create one. Tokens are tied to your account so the rows you create are yours.
A complete working page
Save the file below as index.html, replace YOUR_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>GoRest demo</title>
</head>
<body>
<h1>Users</h1>
<ul id="users"></ul>
<script>
const TOKEN = "YOUR_TOKEN"; // paste yours from /my/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 need 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_TOKEN"; // from /my/tokensSend the bearer token on every request
All write operations and any request that needs your per-token rate-limit budget need an Authorization header. Build it once and pass it into every fetch call:
const headers = {
"Authorization": `Bearer ${TOKEN}`,
"Accept": "application/json",
"Content-Type": "application/json"
};The header Accept: application/json asks for JSON back. The header Content-Type: application/json is needed only when you send a body (POST, PATCH).
Promises and async/await
Every fetch returns a Promise. There are two ways to read the response. Both work, but async/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 uses async/await because it scales better to multi-step flows.
List users with filters
Build the URL with the URL constructor. It URL-encodes the query parameters for you, which avoids small 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 are name, email, gender and status. Strings match by substring; enums (gender, status) match exactly. The response is a plain JSON array; v2 does not wrap responses in a data envelope.
Fetch a single user
Pass the id straight in the path. Handle 404 in your code. 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 are name, email, gender ("male" or "female"), and status ("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 300 requests per minute. When you hit the limit, the API returns 429 with an X-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;
}
});The AbortError caught in the try/catch is the expected result 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 GoRest. - 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.