A Live API, and When It Fails
The Projects page now loads its data from a file on its own site. The last step is data that lives somewhere else and changes without you: a list of repositories from GitHub, the service where many developers keep their code. A repository is one project's folder of files, stored on GitHub. Live data is where everything that can go wrong eventually does. The network is slow, the service is down, a field is empty, a limit runs out. A good page has a plan for each, and says something useful every time.
Think of a departures board at a station. When a train is on time, it shows the time. When a train is late, cancelled or not yet announced, it says so. A board that simply goes blank would leave everyone guessing, and that is the one thing it must never do. This page gives the Projects page a board's manners.
The Endpoint
An endpoint is one address of an API that answers one kind of question. GitHub's public API has one that lists a user's public repositories, and it needs no key or account. The site uses it for octocat, GitHub's own demonstration account, so the examples work for everyone; once it works, put your own GitHub username in its place. This topic is the one place in the book that depends on a real service's address and data shape. They are as checked in September 2026, and a service can change them.
[
{
"name": "Spoon-Knife",
"html_url": "https://github.com/octocat/Spoon-Knife",
"description": "This repo is for demonstration purposes only.",
...
},
{
"name": "test-repo1",
"html_url": "https://github.com/octocat/test-repo1",
"description": null,
...
}
]
Open the endpoint's address in a new tab and the browser shows the answer: a JSON array of five repositories, the ones most recently updated, so which five you see changes over time. Above are two of octocat's repositories, cut down to the three fields the site uses: name, html_url, the repository's page on GitHub, and description. Each real entry has about eighty fields, and the three dots stand for the rest. Notice the second description: it is null, JavaScript's value for nothing. At the time of writing, two of octocat's eight public repositories have no description, so a missing description often turns up among the five.
const reposUrl = "https://api.github.com/users/octocat/repos?sort=updated&per_page=5"; async function loadRepos() { const response = await fetch(reposUrl); if (!response.ok) { throw new Error(`GitHub answered ${response.status}`); } const repos = await response.json(); return repos; }
This is the first of three parts added to js/main.js, placed above main. The address asks for octocat's repositories, sorted by most recently updated, five per page. loadRepos is loadProjects again with another address: fetch, check ok, parse, return. GitHub's answer carries the allow-origin header from the previous topic, so the browser lets the page read it.
Three States Besides Success
A page that shows remote data is always in one of four states. Loading: the request is on its way. Success: data arrived and is shown. Empty: the request worked, but there is nothing to show. Error: something failed. Each needs its own visible answer, and the site's plan is to show its saved projects first, then add the live repositories to the same grid when they arrive.
async function showRepos() { projectsStatus.textContent = "Loading repositories..."; try { const repos = await loadRepos(); if (repos.length === 0) { projectsStatus.textContent = "No public repositories yet."; return; } for (const repo of repos) { projects.push(toProject(repo)); } renderProjects(projects); projectsStatus.textContent = ""; } catch (error) { projectsStatus.textContent = "Could not reach GitHub. Showing saved projects instead."; console.error(error); } }
This is part two. The status says Loading repositories while the request is out. If the answer is an empty array, the status says No public repositories yet, and the saved projects stay as they are. Otherwise each repository is turned into a project with toProject, the third part, and added to the list, and the grid is drawn again with everything in it. If anything fails, from a network error to a refusal from GitHub, the status says Could not reach GitHub, showing saved projects instead, and the details go to the Console.
async function main() { if (!projectsStatus) { return; } projectsStatus.textContent = "Loading projects..."; try { projects = await loadProjects(); renderProjects(projects); } catch (error) { projectsStatus.textContent = "Projects could not be loaded."; console.error(error); return; } await showRepos(); } main();
And this is main in its final form, still at the end of the file. It loads and shows the saved projects exactly as it did when they first moved into a data file, stops if they fail, and otherwise goes on to the repositories. So the visitor sees the saved projects at once, then a line saying repositories are loading, then five more cards. Whatever happens on the network, the page never ends up blank and silent.
Missing Fields
The data decides what the page must handle, not the tutorial. GitHub's own answer contains descriptions that are null, so the code has to expect them. Chapter 7 met the tool for it: two question marks, the ?? operator, which gives the value on its left unless that value is null or undefined, and the value on its right if it is.
function toProject(repo) { return { title: repo.name, description: repo.description ?? "No description", tags: [], url: repo.html_url }; }
The third part turns one repository into the site's own project shape: the repository's name becomes the title, its page becomes the address, and it gets no tags. For a repository with a description, the card shows it. For one whose description is null, the card says No description instead of the word null or an empty gap. Because every card is still built by createCard from Chapter 8, which uses textContent, text from GitHub is shown as text and never run as code, whatever it contains. Data from someone else's server is exactly what that rule is for.
Converting outside data into your own shape at the door is a habit worth keeping. The rest of the site never needs to know GitHub's field names; if GitHub ever renames one, toProject is the only function to change.
Limits and Slow Networks
GitHub allows 60 requests an hour from one internet address without a key, and every load of the Projects page uses one. That is plenty for visitors and surprisingly few for you, reloading while you work. The answer says how many are left, in its headers.
x-ratelimit-limit: 60 x-ratelimit-remaining: 56 x-ratelimit-used: 4 x-ratelimit-reset: 1790237522
In the Network panel, click the request to api.github.com and look under Response headers. These four say the limit is 60, 56 are left, 4 are used, and when the count starts again, written as a count of seconds since the start of 1970, the way many computers store time. When nothing is left, GitHub answers with status 403 or 429, and the remaining count in the headers is 0. The ok check turns that into an error, and the page shows its Could not reach GitHub state. To see it without waiting for the limit, misspell the word repos at the end of the address in reposUrl and reload: GitHub answers 404, the saved projects appear, and then the error line. Put the word back afterwards.
A slow network is the other everyday case. In the Network panel's throttling menu, choose one of the slow presets, such as 3G, and reload: now Loading repositories stays on screen for a while before the new cards arrive. A request can also hang for a very long time. Browsers have a way to give up after a set number of milliseconds, AbortSignal.timeout, but it became Baseline only in April 2024 and counts as widely available from 18 October 2026, so this book names it without relying on it. Until then, the loading message is the honest answer: it stays until something actually happens.
Nothing in the Browser Is Secret
Many APIs ask for a key, a secret string that identifies whoever is calling and often raises limits like GitHub's. The tempting move is to paste one into a script.
// Never do this: every visitor can read it const apiKey = "demo-key-123-not-real";
Put that line at the top of main.js and open the Projects page. Press Ctrl+U on Windows or Cmd+Option+U on macOS to view the page's source, then click the link to js/main.js: the key is right there, on the line where you typed it. The Network panel shows it too, in the file's Response tab, and in the address of any request that carries it. Delete the line afterwards.
That is not a flaw in this site. It is how the web works: to run a page, a browser must download every file the page uses, and anything a browser can download, a person can read. Putting the key in a separate file, or hiding it in a long string, changes nothing. A key in frontend code is a published key. APIs that need a secret have to be called from a server, where the key never leaves; Backend Deep Dive builds that server, and Security for Beginners explains what happens to keys that leak.
| State | What the visitor sees | What the code checked |
|---|---|---|
| Loading | Loading repositories... | the request has not settled yet |
| Success | the saved projects plus five live cards | ok was true and the array has items |
| Empty | No public repositories yet. | ok was true and the array is empty |
| Error | Could not reach GitHub. Showing saved projects instead. | fetch was rejected, or ok was false |
- "If it worked when I tested it, it works." Networks fail, limits run out and fields go missing. Each needs a visible answer on the page.
- "Hiding a key in a separate JavaScript file keeps it private." Every file the page loads can be downloaded and read by anyone.
- "A blank area is a fine error state." The visitor cannot tell a failure from a slow load. Say what happened.
- The site now shows live data and degrades gracefully when it cannot get it: the saved projects are always there.
- The four states and the rule about secrets apply to every API you will use from a page.
Knowledge Check
GitHub answers with status 403 because the hourly limit has run out. What does the site show?
- No public repositories yet, beside the saved projects
- Could not reach GitHub, with the saved projects shown
- Loading repositories, until the hourly limit resets
- An empty grid, since the whole request was refused
GitHub answers with status 200 and an empty array. Which message fits?
- Projects could not be loaded.
- Could not reach GitHub.
- Loading repositories...
- No public repositories yet.
A repository's description is null. What does repo.description ?? "No description" give?
- The text No description
- The word null, as text
- An error, since null is empty
- An empty string, shown blank
Why is an API key written in a page's JavaScript effectively public?
- Browsers pass every script they load on to search engines
- Keys stop working once they are in a script file
- Every file the page loads can be read by anyone
- Only keys written in the HTML itself are hidden away
You got correct