Skip to content

REST-API's

Wat is een API?

Een API (Application Programming Interface) is een manier voor verschillende computerprogramma's om met elkaar te communiceren. Het is als een menu in een restaurant: het toont wat je kunt bestellen (welke verzoeken je kunt doen) en hoe je het moet bestellen (welk formaat je moet gebruiken).

In webapplicaties gebruikt de frontend een API om gegevens op te vragen of te versturen naar de backend.

Voorbeeld: Als je op een knop klikt om vluchten te zoeken, stuurt de frontend een API-verzoek naar de backend: "Geef me alle vluchten van Amsterdam naar Parijs".

Wat is REST?

REST (Representational State Transfer) is een populaire architectuurstijl voor het ontwerpen van API's die gebruik maakt van de bestaande HTTP-protocollen en -methoden. Het werd ontwikkeld door Roy Fielding en is gebaseerd op het idee dat elke resource (zoals een gebruiker, product of vlucht) een unieke URL heeft en dat je verschillende acties kunt uitvoeren door verschillende HTTP-methoden te gebruiken. REST API's zijn stateless, wat betekent dat elke request alle informatie bevat die nodig is om het verzoek te verwerken, zonder dat de server zich eerdere verzoeken hoeft te herinneren. Dit maakt REST API's schaalbaar en betrouwbaar. De vier belangrijkste HTTP-methoden in REST zijn GET voor het ophalen van gegevens, POST voor het aanmaken van nieuwe gegevens, PUT voor het volledig vervangen van bestaande gegevens, en DELETE voor het verwijderen van gegevens.

Alternatieven voor REST

Hoewel REST de meest gebruikte manier is om API's te bouwen, zijn er ook andere technologieën beschikbaar die in bepaalde situaties voordelen kunnen bieden:

GraphQL

GraphQL is een query-taal en runtime voor API's die werd ontwikkeld door Facebook en een andere benadering hanteert dan REST. In plaats van meerdere endpoints voor verschillende resources, heeft GraphQL één enkel endpoint waar clients precies kunnen specificeren welke gegevens ze willen ontvangen. Dit voorkomt het probleem van "over-fetching" (te veel data ophalen) en "under-fetching" (te weinig data ophalen) dat vaak voorkomt bij REST API's. GraphQL is vooral populair bij grote applicaties met complexe datastructuren waar verschillende clients (web, mobiel, desktop) verschillende delen van dezelfde data nodig hebben. Het biedt ook sterke type-checking en introspectie mogelijkheden, waardoor ontwikkelaars precies kunnen zien welke queries mogelijk zijn.

gRPC

gRPC (Google Remote Procedure Call) is een high-performance framework ontwikkeld door Google dat gebruik maakt van HTTP/2 en Protocol Buffers voor het serialiseren van data. In tegenstelling tot REST, dat tekst-gebaseerde JSON gebruikt, werkt gRPC met binaire data, waardoor het veel sneller is en minder bandbreedte gebruikt. gRPC is vooral geschikt voor communicatie tussen microservices en server-to-server communicatie waar performance cruciaal is. Het ondersteunt ook streaming van data in beide richtingen, wat handig is voor real-time applicaties. Echter, gRPC is minder geschikt voor web browsers omdat het speciale libraries vereist en niet direct werkt met standaard web technologieën zoals fetch() in JavaScript.

Voor de meeste webapplicaties blijft REST de beste keuze omdat het eenvoudig te begrijpen is, breed ondersteund wordt door alle programmeertalen en frameworks, en perfect werkt met standaard web browsers en HTTP-tools.

API-endpoints maken in Express

In Express.js maak je API-endpoints door routes te definiëren. Elke route bestaat uit:

  1. Een HTTP-methode (GET, POST, etc.)
  2. Een pad (zoals '/flights' of '/users')
  3. Een functie die het verzoek verwerkt

Hoe definieer je een endpoint en wat is een callback?

Een API-endpoint in Express definieer je door een route op je app-object te registreren met een HTTP-methode en een pad, gevolgd door een callback-functie (ook wel handler genoemd). Bijvoorbeeld:

app.get('/airports', (req, res) => {
  // callback-code hier
});
  • get: de HTTP-methode
  • '/airports': het pad waarop je luistert
  • (req, res) => { ... }: de callback die draait wanneer er een request binnenkomt

Deze callback ontvangt twee objecten:

  • req (request): bevat alle informatie van de binnenkomende client-request, zoals URL-parameters, query-parameters en body-data.
  • res (response): hiermee stuur je een antwoord terug naar de client, bijvoorbeeld via res.json() of res.status().

GET-endpoints

GET-verzoeken worden gebruikt om gegevens op te halen van de server zonder iets te veranderen. Het is de veiligste HTTP-methode omdat het alleen leest en nooit data wijzigt. GET-endpoints zijn perfect voor het ophalen van lijsten, het zoeken naar specifieke items, of het controleren van de status van de server.

// Eenvoudige GET-route voor health check
app.get('/health', (req, res) => {
  res.json({ message: 'Server Online!' });
});

// GET-route die alle luchthavens ophaalt
app.get('/airports', (req, res) => {
  dblayer.getAllAirports((err, airports) => {
    if (err) return res.status(500).json({ error: 'Serverfout' });
    res.json(airports);
  });
});

// GET-route met query parameters
app.get('/flights', (req, res) => {
  const from = req.query.from;  // Leest ?from=AMS uit de URL
  const to = req.query.to;      // Leest ?to=CDG uit de URL

  dblayer.getFlightsFromTo(from, to, (err, flights) => {
    if (err) return res.status(500).json({ error: 'Serverfout' });
    res.json(flights);
  });
});

POST-endpoints

POST-verzoeken worden gebruikt om nieuwe gegevens aan te maken op de server. In tegenstelling tot GET, verandert POST wel degelijk iets in de database. POST-endpoints ontvangen meestal data in de request body en gebruiken deze om nieuwe records toe te voegen. Het is belangrijk om bij POST-endpoints altijd te controleren of de data geldig is voordat je iets opslaat.

// POST-route om nieuwe vlucht aan te maken
app.post('/flights', (req, res) => {
  const flightData = req.body;  // Leest JSON data uit het verzoek

  // Eerst controleren of vlucht al bestaat
  dblayer.flightNrExists(flightData.flightNr, (err, exists) => {
    if (err) return res.status(500).json({ error: 'Serverfout' });
    if (exists) return res.json({ userFeedback: 'Vlucht bestaat al' });

    // Nieuwe vlucht aanmaken
    dblayer.createFlight(flightData, (err, result) => {
      if (err) return res.status(500).json({ error: 'Serverfout' });
      res.json({ userFeedback: 'Vlucht aangemaakt' });
    });
  });
});

PUT en DELETE-endpoints

PUT-verzoeken worden gebruikt om bestaande gegevens volledig te vervangen. Als je een PUT-verzoek stuurt, verwacht de server dat je alle velden van het object meestuurt, niet alleen de velden die je wilt wijzigen. DELETE-verzoeken zijn bedoeld om gegevens permanent te verwijderen van de server. Beide methoden zijn "idempotent", wat betekent dat het uitvoeren van dezelfde actie meerdere keren hetzelfde resultaat geeft.

// PUT-route om vlucht te updaten
app.put('/flights/:id', (req, res) => {
  const flightId = req.params.id;  // Leest :id uit de URL
  const flightData = req.body;

  dblayer.updateFlight(flightId, flightData, (err, result) => {
    if (err) return res.status(500).json({ error: 'Serverfout' });
    res.json({ message: 'Vlucht geüpdatet' });
  });
});

// DELETE-route om vlucht te verwijderen
app.delete('/flights/:id', (req, res) => {
  const flightId = req.params.id;

  dblayer.deleteFlight(flightId, (err, result) => {
    if (err) return res.status(500).json({ error: 'Serverfout' });
    res.json({ message: 'Vlucht verwijderd' });
  });
});

Data lezen uit requests

Wanneer de frontend een verzoek stuurt naar je API, kan er verschillende soorten informatie meekomen. Express.js biedt verschillende manieren om deze data te lezen, afhankelijk van hoe de frontend de informatie heeft verstuurd.

Query Parameters (GET)

Query parameters zijn de meest eenvoudige manier om informatie mee te sturen met een GET-verzoek. Ze staan in de URL na een vraagteken en worden gescheiden door ampersands (&). Dit is handig voor zoekfilters, paginering, of andere opties die de gebruiker kan instellen. Query parameters zijn altijd zichtbaar in de URL, dus gebruik ze nooit voor gevoelige informatie.

/flights?from=AMS&to=CDG&date=2024-01-15

Je kan ze als volgt opvragen in je API-endpoint:

app.get('/flights', (req, res) => {
  const from = req.query.from;    // "AMS"
  const to = req.query.to;        // "CDG"
  const date = req.query.date;    // "2024-01-15"

  // Controleer of verplichte parameters aanwezig zijn
  if (!from || !to) {
    return res.status(400).json({ error: 'Van en naar luchthaven zijn verplicht' });
  }
});

URL Parameters

URL parameters maken deel uit van het pad zelf en worden gebruikt om specifieke resources te identificeren. Je definieert ze in je route met een dubbele punt (:) gevolgd door de parameternaam. Dit is de standaardmanier om bijvoorbeeld een specifiek ID door te geven om één item op te halen, te wijzigen of te verwijderen.

/flights/123  (waar 123 het flight ID is)
app.get('/flights/:id', (req, res) => {
  const flightId = req.params.id;  // "123"

  // Controleer of het ID een geldig nummer is
  if (isNaN(flightId)) {
    return res.status(400).json({ error: 'Ongeldig vlucht ID' });
  }
});

Request Body (POST/PUT)

Voor POST en PUT verzoeken komt de data meestal in de request body als JSON. Dit is de veiligste manier om grotere hoeveelheden data of gevoelige informatie te versturen omdat het niet zichtbaar is in de URL. De request body kan complexe objecten bevatten met meerdere velden en geneste structuren.

app.post('/flights', (req, res) => {
  const flightData = req.body;
  // flightData bevat: { flightNr: "KL123", from: "AMS", to: "CDG", departure: "2024-01-15T10:00:00Z", ... }

  // Valideer de ontvangen data
  if (!flightData.flightNr || !flightData.from || !flightData.to) {
    return res.status(400).json({ error: 'Vluchtnummer, vertrek en aankomst zijn verplicht' });
  }
});

Belangrijk: Vergeet niet app.use(express.json()); toe te voegen aan je Express app om JSON data te kunnen lezen uit de request body!

Data versturen in responses

Nadat je API-endpoint de request heeft verwerkt, moet het een passend antwoord terugsturen naar de frontend. Express.js biedt verschillende manieren om data terug te sturen, waarbij JSON het meest gebruikte formaat is voor moderne webapplicaties.

JSON Response

JSON (JavaScript Object Notation) is het standaard formaat voor API responses omdat het gemakkelijk te lezen is voor zowel mensen als computers, en omdat alle programmeertalen het kunnen verwerken. Je kunt eenvoudige objecten, arrays, of complexe geneste structuren terugsturen. Gebruik altijd de res.json() methode in plaats van res.send() voor JSON data, omdat dit automatisch de juiste Content-Type header instelt.

// Eenvoudig object voor bevestigingen
res.json({ message: 'Vlucht succesvol aangemaakt', id: 123 });

// Array van objecten voor lijsten
res.json([
  { id: 1, name: 'Amsterdam Schiphol', code: 'AMS' },
  { id: 2, name: 'Charles de Gaulle', code: 'CDG' }
]);

// Met specifieke status code voor nieuwe resources
res.status(201).json({ message: 'Vlucht aangemaakt', flightId: 456 });

Error Handling

Goede foutafhandeling is cruciaal voor een betrouwbare API. Zorg ervoor dat je altijd betekenisvolle foutmeldingen teruggeeft en de juiste HTTP status codes gebruikt. Log fouten op de server voor debugging, maar geef nooit technische details prijs aan de frontend die een beveiligingsrisico kunnen vormen.

app.get('/flights', (req, res) => {
  dblayer.getAllFlights((err, flights) => {
    if (err) {
      // Log de technische fout voor ontwikkelaars
      console.error('Database fout bij ophalen vluchten:', err);

      // Stuur gebruiksvriendelijke foutmelding naar frontend
      return res.status(500).json({
        error: 'Er is een probleem opgetreden bij het ophalen van de vluchten. Probeer het later opnieuw.'
      });
    }

    // Succesvol antwoord
    res.json(flights);
  });
});

Status Codes

HTTP status codes zijn driecijferige nummers die de server teruggeeft om aan te geven wat er is gebeurd met het verzoek. Ze zijn ingedeeld in verschillende categorieën: 2xx voor succesvolle verzoeken, 3xx voor omleidingen, 4xx voor fouten aan de kant van de client, en 5xx voor fouten aan de kant van de server. Het correct gebruiken van status codes helpt de frontend om automatisch de juiste actie te ondernemen, zoals het tonen van een foutmelding of het doorsturen naar een andere pagina.

200 OK is de meest gebruikte status code en betekent dat alles goed is gegaan. De server heeft het verzoek succesvol verwerkt en stuurt de gevraagde data terug. Dit is de standaard code voor GET-verzoeken die data terugsturen.

201 Created wordt gebruikt wanneer er een nieuwe resource is aangemaakt, meestal na een POST-verzoek. Het vertelt de frontend dat de actie niet alleen succesvol was, maar dat er ook daadwerkelijk iets nieuws is toegevoegd aan de database.

400 Bad Request geeft aan dat er iets mis is met het verzoek dat de frontend heeft gestuurd. Dit kan zijn omdat er verplichte velden ontbreken, de data het verkeerde formaat heeft, of omdat de waarden ongeldig zijn. De frontend kan deze code gebruiken om validatiefouten te tonen.

404 Not Found betekent dat de gevraagde resource niet bestaat. Dit gebeurt bijvoorbeeld wanneer iemand probeert een vlucht op te halen met een ID dat niet in de database staat, of wanneer een URL-pad niet bestaat.

500 Internal Server Error duidt op een probleem aan de kant van de server. Dit kan een database-fout zijn, een programmeerprobleem, of een andere technische storing. De frontend weet dan dat het probleem niet bij de gebruiker ligt en kan een passende melding tonen.

// Voorbeeld van verschillende status codes in gebruik
app.get('/flights/:id', (req, res) => {
  const flightId = req.params.id;

  if (isNaN(flightId)) {
    return res.status(400).json({ error: 'Vlucht ID moet een nummer zijn' });
  }

  dblayer.getFlightById(flightId, (err, flight) => {
    if (err) {
      return res.status(500).json({ error: 'Serverfout' });
    }

    if (!flight) {
      return res.status(404).json({ error: 'Vlucht niet gevonden' });
    }

    res.status(200).json(flight);  // 200 is standaard, dus je kunt dit weglaten
  });
});