Thuta Learning
ProjectsWeb Developmentintermediate

Mini Project — Part 3: Error Handling and Polish

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Apply Mini Project — Part 3: Error Handling and Polish in a real, hands-on project
  • Write the code yourself and run it to see it work
  • Build out an entire project step by step

Take a moment to think about this

By the end of Part 2, all the CRUD operations work — but if a Network error, a 401 Unauthorized, or a 429 Too Many Requests shows up, the program can still crash. In this part, we'll combine what the Errors lesson taught about parsing an error response body with what the Rate Limit lesson taught about handling a 429 status, and write it all into a single shared wrapper function called request(). Every API call will be routed through this wrapper, so all our error-handling logic lives in one place. Finally, following the Versioning lesson, we'll add the Accept-Version header too, and round out the project by documenting everything — the README.md and the full command list.

Let's build it for real

Write a helper function called request(method, path, body) and gather all the fetch, error-checking, and JSON-parsing logic inside it. When the status is 429, add exponential backoff logic that retries using the Retry-After header (or a fixed delay), up to a maximum of 3 times. For 4xx/5xx errors, pull error.message out of the response body and print it as a user-friendly message with console.error. Refactor createUser/getUsers/updateUser/deleteUser so they all call the request() wrapper, and finally, write up the command list and setup steps in README.md to round out the project.

Code Example

javascript
async function request(method, path, body, attempt = 1) {
  const res = await fetch(`${API_BASE_URL}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${API_TOKEN}`,
      'Content-Type': 'application/json',
      'Accept-Version': 'v1',
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  if (res.status === 429 && attempt <= 3) {
    const delay = attempt * 1000; // exponential-ish backoff
    console.warn(`Rate limited, retrying in ${delay}ms...`);
    await new Promise((r) => setTimeout(r, delay));
    return request(method, path, body, attempt + 1);
  }

  const data = await res.json().catch(() => ({}));
  if (!res.ok) {
    throw new Error(data.error?.message || `Request failed: ${res.status}`);
  }
  return data;
}
You should see
When a rate limit (429) hits, the CLI won't crash — it'll show a retry message and automatically try again, and for errors like 401/404 it'll display a readable message too.

5-Minute Try-It

Within 5 minutes, deliberately set API_TOKEN to something wrong, run request(), and check whether the error message actually reads as user-friendly.

A Quick Word of Caution

If your retry logic runs too aggressively without any delay, it can actually make the rate limit worse — so stick to the backoff delay guideline from the Rate Limit lesson.

Easy traps

  • Forgetting to cap the maximum attempts in your retry logic, which can spiral into an infinite loop
  • Leaving the catch block without a console.log for the error, so failures happen silently

Now Try It Yourself

Within 5 minutes, deliberately set API_TOKEN to something wrong, run request(), and check whether the error message actually reads as user-friendly.

You'll know it worked when: When a rate limit (429) hits, the CLI won't crash — it'll show a retry message and automatically try again, and for errors like 401/404 it'll display a readable message too.

Mini Project — Part 3: Error Handling and Polish | Thuta Learning