Skip to content

Fetch API Calls

Estimated walkthrough time (with discussion): 85-110 minutes

Estimated walkthrough time (with discussion): 85-110 minutes

⚠️ Be mindful of delivery time; this walkthrough can easily run long if discussion expands.

  • Lecture: 25-35 minutes
  • Demo Walkthrough: 60-75 minutes

⚠️ Be mindful of delivery time; this walkthrough can easily run long if discussion expands.

  • This lesson is longer than lesson 02 and adds asynchronous browser-to-API work.
  • Students type through running two servers, smoke-testing the API, building fetch helpers, rendering app data, and handling common errors.
  • The walkthrough needs time for JSON parsing, async flow, and checking both browser and API server output.
  • The estimate allows extra time for slower typing and troubleshooting two-process development setup.

The previous lesson gave students a local REST-style API that we tested by making requests from a *.http file. This lesson moves the request into the browser so JavaScript can ask for server-side data and update the page when a response arrives.

HTTP requests take time. The browser sends a request, the server works on it, and the response arrives later. JavaScript cannot treat that response as an immediate value.

This is why fetch() returns a promise. A promise represents work that may finish later:

  • .then() runs when the promise resolves.
  • .catch() runs when the promise rejects.
  • async functions let code use await to pause inside the function until a promise settles.

The Fetch API returns a Response object first. That response object contains status information and methods for reading the body.

For JSON APIs, the usual flow is:

  1. fetch(endpoint)
  2. Check response.ok
  3. Call response.json()
  4. Render the parsed JavaScript data into the DOM
  • Network Panel for checking API calls
  • Application Panel for local storage, session storage, and cookies
  • Lighthouse Panel for performance
  • Memory Panel

This demo runs two local processes at the same time: JSON Server for the backend API and Vite for the frontend page. The concurrently package lets students run both with one command.

  • Directorydemo
    • Directory.vscode
      • extensions.json
    • Directorycss
      • styles.css
    • Directoryjs
      • main.js
      • utils.js
    • Directorypublic
      • earth.jpg
      • sun.jpg
      • …
    • api-smoke-test.http
    • db.json
    • index.html
    • package.json
    • ReadMe.md
  1. Open the ~/inclass/19/demo folder in VS Code.

  2. Install dependencies.

    Terminal
    pnpm install
  3. Start the frontend and backend together.

    Terminal
    pnpm dev
  4. Confirm the terminal shows both frontend and api-server output.

  5. Open the Vite URL in the browser.

The important package scripts are:

package.json
{
"scripts": {
"frontend": "vite",
"api-server": "json-server --watch db.json --port 3000",
"dev": "concurrently -n frontend,api-server -c blue,green \"pnpm run frontend\" \"pnpm run api-server\""
}
}

Before writing browser JavaScript, prove that the backend API works.

  1. Install the VS Code REST Client extension if VS Code recommends it.

  2. Open api-smoke-test.http.

  3. Send the all-bodies request.

    api-smoke-test.http
    ### GET all bodies
    GET http://localhost:3000/bodies
    Accept: application/json
  4. Send the single-body request.

    api-smoke-test.http
    ### GET one body
    GET http://localhost:3000/bodies/1
    Accept: application/json
  5. Confirm both responses return JSON.

Keep API details in utils.js so main.js can focus on the page behavior.

  1. Create a helper that fetches JSON data.

    js/utils.js
    export function fetchJsonData(endpoint) {
    return fetch(endpoint)
    .then(handleResponse);
    }
  2. Add a shared response handler.

    js/utils.js
    function handleResponse(response) {
    if (!response.ok) {
    const message = `Server responded with ${response.status} (${response.statusText}).`;
    throw new Error(message);
    }
    return response.json();
    }
  3. Add a POST helper using async and await.

    js/utils.js
    export async function postData(endpoint, payload) {
    const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    body: JSON.stringify(payload),
    });
    return handleResponse(response);
    }

This deliberately shows both styles: promise chaining in fetchJsonData() and async/await in postData().

  1. Import the helper functions.

    js/main.js
    import { fetchJsonData, postData } from './utils.js';
  2. Select the DOM elements and define the endpoint.

    js/main.js
    const loadButton = document.querySelector('#load-bodies');
    const addForm = document.querySelector('#add-body');
    const bodyList = document.querySelector('#body-list');
    const statusMessage = document.querySelector('#status-message');
    const endpoint = 'http://localhost:3000/bodies';
  3. Create small rendering helpers for status text and the body list.

    js/main.js
    function showStatus(message, type = 'info') {
    statusMessage.textContent = message;
    statusMessage.dataset.status = type;
    }
    function renderBodies(bodies) {
    bodyList.replaceChildren();
    bodies.forEach((body) => {
    const item = document.createElement('li');
    const image = document.createElement('img');
    image.src = `http://localhost:3000/${body.image}`;
    image.alt = body.name;
    const text = document.createElement('span');
    text.textContent = `${body.name} - ${body.category} - ${body.orbitalPeriod}`;
    item.append(image, text);
    bodyList.append(item);
    });
    }
  4. Add the load handler.

    js/main.js
    function loadHandler() {
    showStatus('Loading bodies...');
    bodyList.innerHTML = '<li>Loading...</li>';
    fetchJsonData(endpoint)
    .then((bodies) => {
    renderBodies(bodies);
    showStatus(`Loaded ${bodies.length} bodies.`, 'success');
    })
    .catch((error) => {
    bodyList.replaceChildren();
    showStatus(`Error: ${error.message}`, 'error');
    });
    }
  5. Add the submit handler.

    js/main.js
    async function submitHandler(event) {
    event.preventDefault();
    const formData = new FormData(event.target);
    const body = Object.fromEntries(formData.entries());
    try {
    await postData(endpoint, body);
    event.target.reset();
    showStatus('Body added.', 'success');
    loadHandler();
    } catch (error) {
    showStatus(`Error: ${error.message}`, 'error');
    }
    }
  6. Attach the event listeners.

    js/main.js
    loadButton.addEventListener('click', loadHandler);
    addForm.addEventListener('submit', submitHandler);

Extend the example by adding DELETE functionality. Add a delete button to each rendered body, create a deleteData() helper, and refresh the list after the server confirms the item was removed.

IssueLikely CauseFix
Failed to fetchJSON Server is not runningRun pnpm dev and check the api-server output
CORS or module errorPage opened with file://Use the Vite URL
Empty listAPI returned an empty arrayCheck db.json and smoke-test the endpoint
POST returns an errorJSON body or headers are wrongCheck Content-Type and JSON.stringify()
Form reloads the pageMissing preventDefault()Add event.preventDefault() in the submit handler

Assigned homework: Complete Homework: Practice Fetch Helpers. It reinforces today’s Fetch API workflow and gives fallback practice for helper functions, JSON parsing, rendering, loading states, and async/API errors.

By the end of this demo, students should understand the path from a browser event to an API request and back to a DOM update. fetch() starts the request, promises manage the waiting, JSON turns the response into JavaScript data, and DOM methods make that data visible to the user.