Database communicatie
Database verbinding in de API¶
Om gegevens op te slaan en op te halen heeft je webapplicatie een database nodig. In dit hoofdstuk leren we hoe de API-laag communiceert met de database-laag.
SQLite Database Setup¶
SQLite is een lichtgewicht, bestandsgebaseerde database die perfect is voor het leren en ontwikkelen van webapplicaties. In tegenstelling tot andere databases zoals MySQL of PostgreSQL, heeft SQLite geen aparte server nodig - alle gegevens worden opgeslagen in één enkel bestand op je computer. Dit maakt het ideaal voor ontwikkeling en kleinere applicaties. SQLite ondersteunt de meeste standaard SQL-functionaliteit en is zeer betrouwbaar, ondanks zijn eenvoud. Veel grote bedrijven gebruiken SQLite voor mobiele apps en embedded systemen omdat het snel is en weinig resources gebruikt.
Om SQLite te gebruiken in Node.js, hebben we de sqlite3
library nodig. Deze library biedt een JavaScript interface om met SQLite databases te communiceren.
const sqlite3 = require('sqlite3');
// Verbinding maken met de database
const db = new sqlite3.Database('./flights.db', (err) => {
if (err) {
console.error('Fout bij verbinden met database:', err.message);
} else {
console.log('Verbonden met de SQLite database.');
}
});
Database-laag functies¶
De database-laag bevat functies die SQL-queries uitvoeren. Elke functie heeft een specifieke taak en gebruikt callbacks om resultaten terug te geven. Dit patroon zorgt ervoor dat de database-operaties asynchroon verlopen, zodat de server niet vastloopt terwijl hij wacht op database-antwoorden.
Gegevens ophalen (SELECT)¶
Voor het ophalen van gegevens gebruiken we SELECT-queries. De db.all()
methode wordt gebruikt wanneer we meerdere rijen verwachten, zoals bij het ophalen van alle luchthavens. De eerste functie toont een eenvoudige query die alle kolommen ophaalt, terwijl de tweede functie laat zien hoe je complexere queries met JOINs en WHERE-clausules kunt gebruiken om specifieke data te filteren.
// Alle luchthavens ophalen
function getAllAirports(callback) {
const query = `
SELECT *
FROM airports
`;
db.all(query, [], (err, rows) => {
if (err) {
console.error('Fout bij ophalen luchthavens:', err);
callback(err);
} else {
callback(null, rows);
}
});
}
// Vluchten tussen twee luchthavens zoeken
function getFlightsFromTo(from, to, callback) {
const query = `
SELECT f.id, f.flight_number, f.departure_time, f.arrival_time
FROM flights f
JOIN airports a1 ON f.departure_airport_id = a1.id
JOIN airports a2 ON f.arrival_airport_id = a2.id
WHERE a1.code = ? AND a2.code = ?
`;
db.all(query, [from, to], (err, rows) => {
if (err) {
console.error('Fout bij ophalen vluchten:', err);
callback(err);
} else {
callback(null, rows);
}
});
}
Gegevens toevoegen (INSERT)¶
Voor het toevoegen van nieuwe gegevens gebruiken we INSERT-queries met de db.run()
methode. Deze methode is geschikt voor queries die geen resultaten teruggeven, maar wel de database wijzigen. Let op het gebruik van vraagtekens (?) als placeholders - dit voorkomt SQL injection aanvallen. De this.lastID
geeft ons het ID van de nieuw aangemaakte record terug.
// Nieuwe vlucht aanmaken
function createFlight(flightData, callback) {
const query = `
INSERT INTO flights (flight_number, departure_airport_id, arrival_airport_id, departure_time, arrival_time)
VALUES (?, ?, ?, ?, ?)
`;
db.run(query, [
flightData.flightNr,
flightData.departureAirportId,
flightData.arrivalAirportId,
flightData.departure,
flightData.arrival
], function(err) {
if (err) {
console.error('Fout bij aanmaken vlucht:', err);
callback(err);
} else {
callback(null, { id: this.lastID });
}
});
}
Controleren of gegevens bestaan¶
Soms willen we alleen weten of een bepaald record bestaat, zonder alle gegevens op te halen. Hiervoor gebruiken we db.get()
die alleen de eerste gevonden rij teruggeeft. Door SELECT 1
te gebruiken in plaats van SELECT *
, maken we de query efficiënter omdat we geen echte data hoeven op te halen. De !!row
constructie converteert het resultaat naar een boolean waarde.
// Controleren of vluchtnummer al bestaat
function flightNrExists(flightNr, callback) {
const query = `
SELECT 1 FROM flights WHERE flight_number = ? LIMIT 1
`;
db.get(query, [flightNr], (err, row) => {
if (err) {
console.error('Fout bij controleren vluchtnummer:', err);
callback(err);
} else {
// !!row converteert naar boolean: true als row bestaat, false als niet
callback(null, !!row);
}
});
}
Database functies aanroepen vanuit API¶
In de API-laag roep je de database functies aan om verzoeken te verwerken. Dit is waar de verbinding wordt gemaakt tussen de HTTP-verzoeken van de frontend en de database-operaties. De API-laag fungeert als een brug die de verzoeken vertaalt naar database-acties en de resultaten teruggeeft in een formaat dat de frontend kan begrijpen.
GET-endpoint met database query¶
Dit voorbeeld toont hoe een eenvoudige GET-endpoint alle luchthavens ophaalt uit de database. De endpoint roept de database-functie aan en handelt zowel succesvolle resultaten als fouten af. Het is belangrijk om altijd foutafhandeling toe te voegen, zodat de frontend een duidelijke melding krijgt als er iets misgaat.
app.get('/airports', (req, res) => {
console.log('Verzoek ontvangen voor alle luchthavens');
// Database functie aanroepen
dblayer.getAllAirports((err, airports) => {
if (err) {
console.error('Database fout:', err);
return res.status(500).json({ error: 'Serverfout' });
}
// Resultaat terugsturen naar client
res.json(airports);
});
});
GET-endpoint met parameters¶
Deze endpoint laat zien hoe je parameters uit de URL kunt lezen en doorgeven aan de database-functie. Query parameters zoals ?from=AMS&to=CDG
worden automatisch geparsed door Express en zijn beschikbaar via req.query
. Deze parameters worden dan gebruikt om specifieke vluchten te zoeken in de database.
app.get('/flights', (req, res) => {
const from = req.query.from;
const to = req.query.to;
console.log(`Zoeken vluchten van ${from} naar ${to}`);
// Database functie aanroepen met parameters
dblayer.getFlightsFromTo(from, to, (err, flights) => {
if (err) {
console.error('Database fout:', err);
return res.status(500).json({ error: 'Serverfout' });
}
res.json(flights);
});
});
POST-endpoint met validatie¶
POST-endpoints zijn complexer omdat ze data ontvangen en validatie nodig hebben voordat ze iets in de database opslaan. Dit voorbeeld toont een typisch patroon: eerst controleren of de data geldig is (in dit geval of het vluchtnummer al bestaat), en dan pas de nieuwe record aanmaken. Dit voorkomt duplicaten en zorgt voor data-integriteit.
app.post('/flights', (req, res) => {
const flightData = req.body;
// Eerst controleren of vlucht al bestaat
dblayer.flightNrExists(flightData.flightNr, (err, exists) => {
if (err) {
return res.status(500).json({ error: 'Serverfout bij controle' });
}
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 bij aanmaken' });
}
res.json({
userFeedback: 'Vlucht succesvol aangemaakt',
flightId: result.id
});
});
});
});
SQL in SQLite3¶
SQLite3 ondersteunt vrijwel alle standaard SQL-functionaliteit die je nodig hebt voor webapplicaties. Je kunt alle geldige SQL-queries gebruiken in SQLite3, inclusief complexe JOINs, subqueries, aggregatiefuncties, en geavanceerde WHERE-clausules. Het enige verschil met andere SQL-databases is dat SQLite3 een beperktere set datatypes heeft (TEXT, INTEGER, REAL, BLOB), maar dit converteert automatisch naar de juiste JavaScript types wanneer je de data ophaalt. Voor de meeste webapplicaties biedt SQLite3 alle functionaliteit die je nodig hebt, van eenvoudige SELECT-statements tot complexe queries met meerdere tabellen.
Error Handling in Database Communicatie¶
Goede foutafhandeling is essentieel voor een betrouwbare webapplicatie. In de database-laag moet je altijd controleren of er fouten optreden bij het uitvoeren van queries, en deze fouten doorgeven aan de callback-functie. Dit zorgt ervoor dat de API-laag weet dat er iets misgegaan is en een passende response kan sturen naar de frontend.
function getAllFlights(callback) {
const query = 'SELECT * FROM flights';
db.all(query, [], (err, rows) => {
if (err) {
// Log de fout voor debugging
console.error('Database query fout:', err.message);
// Geef fout door aan callback
callback(err);
} else {
// Geef resultaat door aan callback
callback(null, rows);
}
});
}
In de API-laag moet je deze fouten opvangen en omzetten naar gebruiksvriendelijke foutmeldingen. Log altijd de technische details voor ontwikkelaars, maar stuur nooit gevoelige informatie naar de frontend. Gebruik de juiste HTTP status codes zodat de frontend weet hoe het moet reageren op verschillende soorten fouten.
app.get('/flights', (req, res) => {
dblayer.getAllFlights((err, flights) => {
if (err) {
// Log de fout
console.error('Fout bij ophalen vluchten:', err);
// Stuur gebruiksvriendelijke foutmelding
return res.status(500).json({
error: 'Er ging iets mis bij het ophalen van de vluchten'
});
}
res.json(flights);
});
});
Module Exports¶
Om je database functies te kunnen gebruiken in andere bestanden, moet je ze exporteren uit je database-laag bestand. Node.js gebruikt het CommonJS module systeem, waarbij je module.exports
gebruikt om functies beschikbaar te maken voor andere bestanden. Dit zorgt voor een nette scheiding tussen verschillende onderdelen van je applicatie.
In je API-bestand importeer je vervolgens de database-laag met require()
. Het is een goede gewoonte om een duidelijke naam te gebruiken zoals dblayer
zodat het meteen duidelijk is dat je database-functies aanroept.
Met deze kennis kun je een complete verbinding maken tussen je API-endpoints en je database, zodat je webapplicatie gegevens kan opslaan en ophalen.