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 filesProject files
<!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>
# 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