Concepts 🚀 Project 🧮 Labs
📚 Learn / Mini Projects / Frontend REST Client (fetch + render table)

Frontend REST Client (fetch + render table)

A vanilla JS frontend that calls a REST API, renders results in a table, and supports add/delete — no framework.

🚀 Mini Project · 2 files

Project files

📄 index.html html
<!DOCTYPE html>
<html>
<head>
  <title>Users</title>
  <style>
    body { font-family: sans-serif; max-width: 600px; margin: 2rem auto; }
    table { width: 100%; border-collapse: collapse; }
    th, td { padding: 0.5rem; text-align: left; border-bottom: 1px solid #ddd; }
    form { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
    input { padding: 0.4rem; flex: 1; }
    button { padding: 0.4rem 1rem; cursor: pointer; }
  </style>
</head>
<body>
  <h1>Users</h1>
  <form id='add'>
    <input id='name' placeholder='name' required>
    <input id='email' placeholder='email' required>
    <button>Add</button>
  </form>
  <table>
    <thead><tr><th>ID</th><th>Name</th><th>Email</th><th></th></tr></thead>
    <tbody id='rows'></tbody>
  </table>

  <script>
    const API = 'http://localhost:8000';  // change to your API URL

    async function load() {
      const res = await fetch(API + '/users');
      const users = await res.json();
      const rows = document.getElementById('rows');
      rows.innerHTML = users.map(u => `
        <tr>
          <td>${u.id}</td>
          <td>${u.name}</td>
          <td>${u.email}</td>
          <td><button onclick='del(${u.id})'>x</button></td>
        </tr>
      `).join('');
    }

    async function del(id) {
      await fetch(`${API}/users/${id}`, {method: 'DELETE'});
      load();  // refresh
    }

    document.getElementById('add').addEventListener('submit', async (e) => {
      e.preventDefault();
      const name = document.getElementById('name').value;
      const email = document.getElementById('email').value;
      await fetch(API + '/users', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({name, email}),
      });
      e.target.reset();
      load();  // refresh
    });

    load();  // initial paint
  </script>
</body>
</html>
📄 main.py python
# Use the project-crud-api main.py here
# (See 'CRUD API with FastAPI + SQLAlchemy' mini-project)

How it works

This is the standard SPA (single-page app) pattern without a framework. The flow: (1) load() fetches all users from the API and renders them as table rows using template literals. The .map().join('') idiom turns an array into an HTML string. (2) The Add form submits a POST request with JSON body, then calls load() to refresh. (3) The delete button calls del(id) which sends DELETE, then refreshes. The key insight: the table is always a reflection of the API state. Every mutation (add/delete) is followed by a re-fetch. This is 'request → render' — simpler than the localStorage todo app because the source of truth is the server, not the browser. To add: loading spinners, error handling (try/catch around fetch), and optimistic updates (update the DOM first, then send the request).

⚡ Open in Notebook
🔍 Where this is used in OpenBenchML OpenBenchML's dashboard, leaderboard, my_models pages all follow this pattern: fetch JSON from the API, render into a table or card grid.