Skip to content

Client-side API-calls

Hoe de frontend communiceert met de backend

De frontend (HTML + JavaScript) communiceert met de backend via HTTP-verzoeken. Dit gebeurt asynchroon, wat betekent dat de pagina niet hoeft te wachten tot het verzoek klaar is.

De Fetch API en async/await

In moderne JavaScript maken we al onze HTTP-verzoeken met de Fetch API in combinatie met async/await. Zo schrijven we asynchrone code op één eenduidige en leesbare manier.

Eenduidige helper-functie

Definieer één algemene functie om data op te halen en JSON te verwerken. Deze apiRequest-functie behandelt:

  1. Fetch: het versturen van het HTTP-verzoek
  2. Statuscontrole: controleren of response.ok (status 200–299), anders een fout gooien
  3. JSON-conversie: het omzetten van de response naar een JavaScript-object
  4. Foutafhandeling: met try/catch vangen we netwerk- en HTTP-fouten op
async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, options);
    if (!response.ok) {
      throw new Error(`HTTP-fout! status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Fout bij API-call:', error);
    throw error;
  }
}

Voorbeeldgebruik

Gebruik apiRequest voor elk type API-call—GET, POST, etc.—door alleen de URL en eventueel opties mee te geven.

GET-request

async function getAirports() {
  try {
    const airports = await apiRequest('/airports');
    displayAirports(airports);
  } catch (error) {
    console.error('Fout bij ophalen luchthavens:', error);
  }
}

Deze code demonstreert een typische GET-request. De apiRequest functie wordt aangeroepen met het /airports endpoint. De response, die een lijst van luchthavens bevat, wordt vervolgens doorgegeven aan de displayAirports functie om de data op de pagina weer te geven. Eventuele fouten tijdens het ophalen van de data worden afgevangen en in de console gelogd.

POST-request met JSON-payload

async function createFlight(flightData) {
  try {
    const options = {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(flightData)
    };
    const result = await apiRequest('/flights', options);
    console.log('Server feedback:', result);
  } catch (error) {
    console.error('Fout bij creëren vlucht:', error);
  }
}

Deze code illustreert een POST-request met een JSON-payload. De options variabele bevat de configuratie voor de request, inclusief de method (POST), de headers (om aan te geven dat we JSON versturen), en de body (de data die we versturen, omgezet naar een JSON string met JSON.stringify). De apiRequest functie wordt aangeroepen met het /flights endpoint en de options. De server feedback wordt vervolgens in de console gelogd. Eventuele fouten worden afgevangen en in de console gelogd.

Op deze manier kan je met slechts één functie heel wat client-side API-calls implementeren.

HTTP GET Requests

GET-verzoeken worden gebruikt om gegevens op te halen van de server. Ze zijn de meest gebruikte type HTTP-verzoeken omdat ze veilig zijn en geen data wijzigen op de server.

Eenvoudige GET-request

Dit voorbeeld toont de basis van een GET-request. We maken een verzoek naar het /airports endpoint met de apiRequest functie. De displayAirports() functie wordt aangeroepen om de ontvangen data te tonen in de gebruikersinterface. Foutafhandeling zorgt ervoor dat eventuele problemen netjes worden gelogd.

async function getAirports() {
  try {
    const airports = await apiRequest('/airports');

    // Toon de luchthavens in een tabel
    displayAirports(airports);
  } catch (error) {
    console.error('Fout bij ophalen luchthavens:', error);
  }
}

GET-request met query parameters

Query parameters laten je specifieke filters of opties meesturen met je verzoek. In dit voorbeeld lezen we waarden uit HTML form elementen en voegen deze toe aan de URL. De template literal syntax (${variable}) maakt het gemakkelijk om dynamische URL's te bouwen. De server kan deze parameters gebruiken om gefilterde resultaten terug te sturen.

async function getFlights() {
  const from = document.getElementById('from').value;
  const to = document.getElementById('to').value;
  const url = `/flights?from=${from}&to=${to}`;

  try {
    // Query parameters toevoegen aan de URL
    const flights = await apiRequest(url);

    displayFlights(flights);
  } catch (error) {
    console.error('Fout bij ophalen vluchten:', error);
  }
}

Health check voorbeeld

Een health check is een eenvoudige manier om te controleren of de server online en bereikbaar is. Dit voorbeeld toont hoe je de status van de server kunt controleren en de gebruiker feedback kunt geven. Als het verzoek mislukt, wordt automatisch een "Server offline" bericht getoond. De event listener zorgt ervoor dat deze check automatisch wordt uitgevoerd wanneer de pagina laadt.

async function performHealthCheck() {
  try {
    const result = await apiRequest('/health');
    document.getElementById('health-status').textContent = result.message;
  } catch (error) {
    document.getElementById('health-status').textContent = 'Server offline';
  }
}

// Health check uitvoeren wanneer pagina laadt
window.addEventListener('DOMContentLoaded', performHealthCheck);

HTTP POST Requests met JSON

POST-verzoeken worden gebruikt om nieuwe gegevens naar de server te sturen. In tegenstelling tot GET-verzoeken, bevatten POST-verzoeken data in de request body en kunnen ze de staat van de server wijzigen.

JSON payload versturen

Dit voorbeeld toont hoe je een nieuwe vlucht kunt aanmaken door data te verzamelen uit HTML formulier elementen en deze te versturen als JSON. Let op de drie belangrijke onderdelen van een POST-request: de method: 'POST', de Content-Type header die aangeeft dat we JSON versturen, en de body die de data bevat. JSON.stringify() converteert het JavaScript object naar een JSON string die de server kan begrijpen.

async function createFlight() {
  // Data verzamelen uit formulier
  const flightData = {
    flightNr: "PP" + Math.floor(Math.random() * 5000),
    from: document.getElementById('add-from').value,
    to: document.getElementById('add-to').value,
    departure: document.getElementById('add-dep').value,
    arrival: document.getElementById('add-arr').value
  };

  try {
    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(flightData)
    };

    const result = await apiRequest('/flights', options);
    // Toon resultaat aan gebruiker
    document.getElementById('add-response').textContent = result.userFeedback;

  } catch (error) {
    console.error('Fout bij aanmaken vlucht:', error);
    document.getElementById('add-response').textContent = 'Er ging iets mis';
  }
}

POST-request met formulier data

Dit voorbeeld toont een meer algemene POST-request voor het versturen van formulierdata. De structuur is vergelijkbaar met het vorige voorbeeld.

async function submitForm() {
  const formData = {
    name: document.getElementById('name').value,
    email: document.getElementById('email').value,
    message: document.getElementById('message').value
  };

  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(formData)
  };

  try {
    const result = await apiRequest('/contact', options);
    alert('Bericht verzonden!');
  } catch (error) {
    console.error('Fout:', error);
    alert('Netwerkfout');
  }
}

JSON Data verwerken

Nadat je data hebt ontvangen van de server, moet je deze verwerken en tonen aan de gebruiker. JSON data komt binnen als JavaScript objecten en arrays, waardoor het gemakkelijk is om ermee te werken.

JSON Response lezen

Wanneer je .json() aanroept op een fetch response, krijg je JavaScript objecten terug die je direct kunt gebruiken. In dit voorbeeld ontvangen we een array van luchthaven objecten. Elke luchthaven heeft eigenschappen zoals code, name, en city die je kunt benaderen met de punt-notatie. De console.log toont de structuur van de data, wat handig is voor debugging.

async function getAirports() {
  try {
    const airports = await apiRequest('/airports');
    // airports is nu een JavaScript array met objecten
    console.log(airports);
    // Voorbeeld output: [
    //   { id: 1, code: 'AMS', name: 'Amsterdam Schiphol', city: 'Amsterdam' },
    //   { id: 2, code: 'CDG', name: 'Charles de Gaulle', city: 'Paris' }
    // ]
  } catch (error) {
    console.error('Fout bij ophalen luchthavens:', error);
  }
}

Data tonen in HTML

Deze functie toont hoe je JSON data kunt omzetten naar HTML tabel rijen. We gebruiken querySelector om het tbody element te vinden, maken het eerst leeg, en voegen dan voor elke luchthaven een nieuwe rij toe. Template literals (backticks) maken het gemakkelijk om HTML strings te bouwen met variabelen. Ten slotte maken we de tabel zichtbaar door de CSS display property te wijzigen.

function displayAirports(airports) {
  const tbody = document.querySelector('#airports-table tbody');
  tbody.innerHTML = ''; // Tabel leegmaken

  airports.forEach(airport => {
    tbody.innerHTML += `
      <tr>
        <td>${airport.code}</td>
        <td>${airport.name}</td>
        <td>${airport.city}</td>
        <td>${airport.country}</td>
      </tr>
    `;
  });

  // Tabel zichtbaar maken
  document.getElementById('airports-table').style.display = 'table';
}

Dropdown menu's (select elementen) zijn een veelgebruikte manier om gebruikers keuzes te laten maken uit API data. Deze functie toont hoe je meerdere dropdowns kunt vullen met dezelfde data. We beginnen met een standaard "kies een optie" item, en voegen dan voor elke luchthaven een option element toe. De value attribute bevat de luchthaven code die we later kunnen gebruiken in API calls.

function fillDropdowns(airports) {
  const fromSelect = document.getElementById('from');
  const toSelect = document.getElementById('to');

  // Dropdowns leegmaken
  fromSelect.innerHTML = '<option value="">-- Kies luchthaven --</option>';
  toSelect.innerHTML = '<option value="">-- Kies luchthaven --</option>';

  // Opties toevoegen
  airports.forEach(airport => {
    const option = `<option value="${airport.code}">${airport.code} - ${airport.city}</option>`;
    fromSelect.innerHTML += option;
    toSelect.innerHTML += option;
  });
}

Error Handling

Goede foutafhandeling is cruciaal voor een betrouwbare gebruikerservaring. Je moet rekening houden met verschillende soorten fouten: netwerkproblemen, server fouten, en ongeldige responses.

Response status controleren

Deze functie toont hoe je kunt controleren of een API-verzoek succesvol was. De response.ok property is true voor status codes 200-299. Als de response niet ok is, gooien we een error met de status code. Dit zorgt ervoor dat de catch block wordt uitgevoerd en je de fout kunt afhandelen. Het is belangrijk om altijd de response status te controleren voordat je probeert de data te lezen.

async function getData() {
  try {
    const response = await fetch('/api/data');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fout bij ophalen data:', error);
    throw error;
  }
}

Verschillende fout types afhandelen

Dit voorbeeld toont hoe je verschillende HTTP status codes kunt afhandelen om de gebruiker specifieke feedback te geven. Status code 400 betekent meestal dat er iets mis is met de data die je hebt verstuurd, terwijl 500 een server probleem aangeeft. Door verschillende responses te geven voor verschillende fouten, help je de gebruiker begrijpen wat er mis ging en wat ze kunnen doen om het op te lossen.

async function createItem(itemData) {
  try {
    const response = await fetch('/items', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(itemData)
    });

    const result = await response.json();

    if (response.status === 400) {
      // Bad request - toon validatie fouten
      showValidationErrors(result.errors);
    } else if (response.status === 500) {
      // Server error
      showMessage('Er ging iets mis op de server');
    } else if (response.ok) {
      // Success
      showMessage('Item succesvol aangemaakt');
    }
  } catch (error) {
    // Netwerkfout
    showMessage('Geen verbinding met server');
  }
}

Event Handlers koppelen

Event handlers verbinden je JavaScript functies met gebruikersacties zoals klikken, formulieren versturen, en pagina laden. Dit maakt je webapplicatie interactief.

Button clicks

Er zijn twee manieren om click events af te handelen. De addEventListener methode is de moderne, aanbevolen manier omdat je meerdere listeners kunt toevoegen en meer controle hebt. De onclick attribute in HTML is eenvoudiger maar minder flexibel. Gebruik bij voorkeur addEventListener voor betere code organisatie.

// Event listener toevoegen aan knop
document.getElementById('search-btn').addEventListener('click', getFlights);

// Of direct in HTML: <button onclick="getFlights()">Zoeken</button>

Form submission

Formulieren hebben standaard gedrag dat de pagina herlaadt wanneer ze worden verstuurd. Met event.preventDefault() voorkom je dit gedrag zodat je de data kunt verwerken met JavaScript. Dit is essentieel voor single-page applications waar je niet wilt dat de pagina herlaadt bij elke actie.

document.getElementById('flight-form').addEventListener('submit', async (event) => {
  event.preventDefault(); // Voorkom standaard form submission

  await createFlight();
});

Page load events

Er zijn verschillende momenten waarop je code kunt uitvoeren tijdens het laden van de pagina. DOMContentLoaded wordt uitgevoerd zodra de HTML is geladen, maar voordat afbeeldingen en andere resources klaar zijn. load wacht tot alles volledig geladen is. Voor de meeste API calls is DOMContentLoaded voldoende en sneller.

// Wanneer DOM klaar is
document.addEventListener('DOMContentLoaded', () => {
  performHealthCheck();
  getAirports();
});

// Wanneer hele pagina geladen is (inclusief afbeeldingen)
window.addEventListener('load', () => {
  console.log('Pagina volledig geladen');
});

Complete voorbeeld: Vluchten App

Dit complete voorbeeld toont hoe alle concepten uit dit hoofdstuk samenkomen in een werkende vluchten-applicatie. De code demonstreert de volledige cyclus van het laden van data bij het opstarten, het zoeken met gebruikersinput, en het toevoegen van nieuwe records. Let op hoe elke functie een specifieke verantwoordelijkheid heeft en hoe foutafhandeling consistent wordt toegepast door de hele applicatie.

// Alle luchthavens ophalen en dropdowns vullen
async function loadAirports() {
  try {
    const response = await fetch('/airports');
    const airports = await response.json();

    fillDropdowns(airports);
    displayAirports(airports);
  } catch (error) {
    console.error('Fout bij laden luchthavens:', error);
  }
}

// Vluchten zoeken
async function searchFlights() {
  const from = document.getElementById('from').value;
  const to = document.getElementById('to').value;

  if (!from || !to) {
    alert('Kies beide luchthavens');
    return;
  }

  try {
    const response = await fetch(`/flights?from=${from}&to=${to}`);
    const flights = await response.json();

    if (flights.length === 0) {
      document.getElementById('message').textContent = 'Geen vluchten gevonden';
    } else {
      displayFlights(flights);
    }
  } catch (error) {
    console.error('Fout bij zoeken vluchten:', error);
  }
}

// Nieuwe vlucht aanmaken
async function addFlight() {
  const flightData = {
    flightNr: 'PP' + Math.floor(Math.random() * 5000),
    from: document.getElementById('add-from').value,
    to: document.getElementById('add-to').value,
    departure: document.getElementById('add-dep').value,
    arrival: document.getElementById('add-arr').value
  };

  try {
    const response = await fetch('/flights', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(flightData)
    });

    const result = await response.json();
    document.getElementById('add-response').textContent = result.userFeedback;
  } catch (error) {
    console.error('Fout bij toevoegen vlucht:', error);
  }
}

// App initialiseren
document.addEventListener('DOMContentLoaded', loadAirports);