Build a Live Dashboard with JavaScript and csv-api
Fetch data from your csv-api endpoint and render it in a dynamic frontend using vanilla JavaScript.
What we'll build
In this tutorial, we'll build a live product dashboard that fetches data from a csv-api endpoint and renders a searchable, sortable table — all with vanilla JavaScript and no build tools.
Since csv-api includes CORS headers on all API responses, you can fetch data directly from any frontend application, static site, or single-page app.
Prerequisites
- A csv-api account with at least one uploaded dataset
- An API key (see the authentication tutorial)
- Basic knowledge of HTML and JavaScript
Step 1: The HTML skeleton
Create an index.html file with a search input, a table placeholder, and pagination controls:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Product Dashboard</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 900px; margin: 2rem auto; padding: 0 1rem; }
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
th, td { padding: 0.5rem 1rem; text-align: left; border-bottom: 1px solid #e5e7eb; }
th { font-size: 0.75rem; text-transform: uppercase; color: #6b7280; cursor: pointer; }
th:hover { color: #111; }
input { padding: 0.5rem 1rem; border: 1px solid #d1d5db; border-radius: 0.5rem; width: 100%; max-width: 300px; }
.pagination { display: flex; gap: 0.5rem; align-items: center; margin-top: 1rem; }
.pagination button { padding: 0.25rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.375rem; background: white; cursor: pointer; }
.pagination button:disabled { opacity: 0.4; cursor: default; }
</style>
</head>
<body>
<h1>Product Dashboard</h1>
<input type="text" id="search" placeholder="Search products...">
<table>
<thead id="table-head"></thead>
<tbody id="table-body"></tbody>
</table>
<div class="pagination">
<button id="prev-btn" onclick="changePage(-1)">Previous</button>
<span id="page-info"></span>
<button id="next-btn" onclick="changePage(1)">Next</button>
</div>
<script src="app.js"></script>
</body>
</html>
Step 2: Fetching data from csv-api
Create an app.js file. We'll start with the core fetch function:
const API_BASE = "https://csv-api.com/api/v1/datasets/YOUR_PUBLIC_ID/records";
const API_KEY = "YOUR_API_KEY";
let currentPage = 1;
let currentSort = "name";
let searchTerm = "";
async function fetchData() {
const params = new URLSearchParams({
page: currentPage,
per_page: 20,
sort: currentSort
});
// Add search filter if present
if (searchTerm) {
params.set("filter[name][like]", searchTerm);
}
const response = await fetch(`${API_BASE}?${params}`, {
headers: { "Authorization": `Bearer ${API_KEY}` }
});
if (!response.ok) {
console.error("API error:", response.status);
return null;
}
return response.json();
}
Step 3: Rendering the table
Add functions to render the data into our HTML table and handle pagination:
async function render() {
const result = await fetchData();
if (!result) return;
const { data, meta } = result;
const thead = document.getElementById("table-head");
const tbody = document.getElementById("table-body");
// Build header from first record's keys
if (data.length > 0) {
const columns = Object.keys(data[0]);
thead.innerHTML = `<tr>${columns.map(col =>
`<th onclick="sortBy('${col}')">${col}${
currentSort === col ? " ▲" :
currentSort === "-" + col ? " ▼" : ""
}</th>`
).join("")}</tr>`;
}
// Build rows
tbody.innerHTML = data.map(row =>
`<tr>${Object.values(row).map(val =>
`<td>${val ?? ""}</td>`
).join("")}</tr>`
).join("");
// Update pagination
document.getElementById("page-info").textContent =
`Page ${meta.page} of ${meta.total_pages} (${meta.total} records)`;
document.getElementById("prev-btn").disabled = meta.page <= 1;
document.getElementById("next-btn").disabled = meta.page >= meta.total_pages;
}
function sortBy(column) {
// Toggle between ascending and descending
currentSort = currentSort === column ? `-${column}` : column;
currentPage = 1;
render();
}
function changePage(delta) {
currentPage += delta;
render();
}
// Search with debounce
let searchTimeout;
document.getElementById("search").addEventListener("input", (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchTerm = e.target.value;
currentPage = 1;
render();
}, 300);
});
// Initial render
render();
Step 4: Try it out
Open index.html in your browser (or serve it with any static file server). You should see your CSV data rendered as a table with:
- Click any column header to sort (click again to reverse)
-
Type in the search box to filter by name (uses the
likeoperator) - Use Previous/Next buttons to navigate pages
Security note for production
In the example above, the API key is embedded directly in the JavaScript file. This is fine for internal tools or prototypes, but for public-facing apps you should proxy API requests through your own backend to keep the key secret.
Example with a simple Express proxy:
// server.js
app.get("/api/products", async (req, res) => {
const response = await fetch(
`https://csv-api.com/api/v1/datasets/YOUR_ID/records?${req.query}`,
{ headers: { "Authorization": `Bearer ${process.env.CSV_API_KEY}` } }
);
const data = await response.json();
res.json(data);
});
Next steps
This basic dashboard demonstrates the core pattern: fetch, render, interact. From here you could add more filter controls, charting with a library like Chart.js, or integrate csv-api into a React/Vue/Svelte app. Check out the tutorial on field selection and performance to learn how to optimize your API calls.