Codeskill

Learn to code, step by step

Mini project: API client

This mini project builds a small API client – fetch data from a public HTTP API, show loading and error states, and keep the fetch logic separate from the DOM. It pulls together modules, async patterns, and DOM updates from these tutorials.

Pick an API

Use a read-only public API – JSONPlaceholder (jsonplaceholder.typicode.com), Open-Meteo for weather, or a static JSON file you control. Avoid APIs that need secret keys in the browser.

Separate api from UI

// js/api.js
export async function getPosts(limit = 10) {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`
  );
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
// js/render.js
export function renderPosts(container, posts) {
  container.replaceChildren(
    ...posts.map((post) => {
      const article = document.createElement('article');
      article.innerHTML = `
        <h2>${escapeHtml(post.title)}</h2>
        <p>${escapeHtml(post.body)}</p>`;
      return article;
    })
  );
}

function escapeHtml(text) {
  return text.replace(/[&<>"']/g, (ch) => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  }[ch]));
}

Loading and error UI

// js/main.js
import { getPosts } from './api.js';
import { renderPosts } from './render.js';

const statusEl = document.querySelector('#status');
const listEl = document.querySelector('#posts');

async function load() {
  statusEl.textContent = 'Loading…';
  listEl.replaceChildren();
  try {
    const posts = await getPosts(12);
    renderPosts(listEl, posts);
    statusEl.textContent = '';
  } catch (err) {
    statusEl.textContent = 'Could not load posts. Try again.';
    console.error(err);
  }
}

document.querySelector('#retry').addEventListener('click', load);
load();

Abort on navigation or retry

let controller;

async function load() {
  controller?.abort();
  controller = new AbortController();
  const posts = await getPosts(12, { signal: controller.signal });
  // ...
}

Extend getPosts to accept signal and pass it to fetch so rapid retries do not race.

Acceptance checklist

  • Initial load shows a loading state; success clears it.
  • Failed fetch shows a human message and a retry control.
  • HTML from the API is escaped before insertion (text content or escape helper).
  • Fetch and render live in separate modules with a thin main.js.
  • Optional: cache last successful response in sessionStorage for instant paint on refresh.

That completes Going further with JavaScript. The advanced tutorials goes deeper on architecture, workers, security, and offline-capable apps – when you are ready to treat the browser as a serious application platform.

PreviousMini project: interactive UI