Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
| de:modul:m290_guko:learningunits:lu17:theorie:a_intro [2026/08/13 16:06] – gelöscht - Externe Bearbeitung (Unbekanntes Datum) 127.0.0.1 | de:modul:m290_guko:learningunits:lu17:theorie:a_intro [2026/08/13 16:08] (aktuell) – ↷ Links angepasst, weil Seiten im Wiki verschoben wurden 216.73.216.62 | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| + | ====== LU17 – CRUD mit Express & MySQL ====== | ||
| + | |||
| + | ===== Lernziele ===== | ||
| + | * Sie können Ihre **MySQL-Datenbank** aus einem Express-Server heraus ansprechen. | ||
| + | * Sie können die CRUD-Routen aus LU16 ('' | ||
| + | * Sie setzen einfache **Validierung** ein (Pflichtfelder prüfen). | ||
| + | * Sie verwenden passende **HTTP-Statuscodes** (200, 201, 400, 404, 500). | ||
| + | * Sie testen alle Operationen mit **Postman**. | ||
| + | |||
| + | ===== Rückblick auf LU16 ===== | ||
| + | In LU16 haben Sie eine API für Social-Media-Posts gebaut – aber noch ohne Datenbank: | ||
| + | |||
| + | * Daten waren in einer **JavaScript-Liste '' | ||
| + | * Ihre Routen waren z.B.: | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | In LU17 ersetzen Sie diese In-Memory-Liste durch eine echte Datenbank: | ||
| + | |||
| + | * Die Daten kommen aus der **MySQL-Tabelle '' | ||
| + | * Ihr Express-Server ist die **Brücke** zwischen Client (Postman) und Datenbank. | ||
| + | * Die gleiche Idee bleibt: **HTTP-Anfrage → Route → Logik/SQL → HTTP-Antwort**. | ||
| + | |||
| + | |||
| + | {{de: | ||
| + | |||
| + | ===== HTTP-Statuscodes ===== | ||
| + | Wenn Sie mit Postman testen, sehen Sie immer einen Statuscode. Dieser Code ist Ihr „Kurzfazit“, | ||
| + | |||
| + | {{ de: | ||
| + | |||
| + | <WRAP center box round 80%> | ||
| + | ^ Situation ^ Typischer Statuscode ^ Bedeutung ^ | ||
| + | | Erfolgreich Daten gelesen (z.B. GET) | **200** | OK (Daten kommen zurück) | | ||
| + | | Erfolgreich Datensatz erstellt (POST) | **201** | Created (Neuer Datensatz wurde erstellt) | | ||
| + | | Erfolgreich aktualisiert (PUT) | **200** | OK (Update hat geklappt) | | ||
| + | | Erfolgreich gelöscht (DELETE) | **200** | OK (Löschen hat geklappt; Sie können zur Kontrolle z.B. das gelöschte Objekt oder eine Message zurückgeben) | | ||
| + | | Pflichtfelder fehlen / ungültige Eingabe | **400** | Bad Request (Client hat falsche/ | ||
| + | | ID existiert nicht | **404** | Not Found (Datensatz wurde nicht gefunden) | | ||
| + | | Datenbank-/ | ||
| + | </ | ||
| + | |||
| + | ===== Vorbereitung: | ||
| + | Für die Verbindung mit MySQL benötigen wir eine bestehende Datenbank. Wir arbeiten mit der Datenbank " | ||
| + | |||
| + | <WRAP center round download 60%> | ||
| + | Laden Sie hier den SQL-Dump herunter und lassen Sie das Skript in Webstorm laufen via //Run SQL Script...// (eine Anleitung, wie das prinzipiell geht finden Sie in LU09). | ||
| + | {{ de: | ||
| + | </ | ||
| + | |||
| + | Das ERD der Datenbank sieht so aus: | ||
| + | {{ de: | ||
| + | |||
| + | Eine mögliche Tabellenstruktur für die Tabelle " | ||
| + | |||
| + | <WRAP center box round 80%> | ||
| + | ^ Spalte ^ Datentyp ^ Beschreibung ^ | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | </ | ||
| + | |||
| + | <WRAP center round important 60%> | ||
| + | Achtung: Die Primär-Spalte heisst neu '' | ||
| + | </ | ||
| + | |||
| + | ===== Schritt 1: mysql2 installieren ===== | ||
| + | Damit Node.js mit MySQL sprechen kann, verwenden wir '' | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code bash> | ||
| + | npm install mysql2 | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | ===== Schritt 2: connect.js erstellen (DB-Verbindung auslagern) ===== | ||
| + | Damit '' | ||
| + | Diese Datei erstellt **eine Verbindung** und exportiert sie, damit Sie sie in Ihren Routes mit '' | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | import mysql from ' | ||
| + | |||
| + | // Hinweis: Verwenden Sie hier Ihren AppUser (nicht root). | ||
| + | // Port 3306 ist der Standard-Port für MySQL auf localhost. | ||
| + | const db = mysql.createConnection({ | ||
| + | host: ' | ||
| + | user: ' | ||
| + | password: ' | ||
| + | port: 3306, | ||
| + | database: ' | ||
| + | }); | ||
| + | |||
| + | // connect() öffnet die Verbindung und gibt bei Erfolg/ | ||
| + | db.connect((err) => { | ||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return; | ||
| + | } | ||
| + | console.log(' | ||
| + | }); | ||
| + | |||
| + | export { db }; | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | **Was passiert hier?** | ||
| + | * '' | ||
| + | * In der LB03 sollen Sie **nicht** als '' | ||
| + | |||
| + | |||
| + | ===== Schritt 3: index.js vorbereiten (Express + DB verwenden) ===== | ||
| + | Sie bauen auf dem Setup aus LU16 auf: | ||
| + | |||
| + | * '' | ||
| + | * Sie importieren '' | ||
| + | * __Löschen__ Sie die Test-Tabelle aus LU16 '' | ||
| + | |||
| + | |||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | import express from ' | ||
| + | import { db } from ' | ||
| + | |||
| + | const app = express(); | ||
| + | const port = 3000; | ||
| + | |||
| + | app.use(express.json()); | ||
| + | |||
| + | // Test-Route: schnell prüfen, ob der Server läuft | ||
| + | app.get('/', | ||
| + | res.send(' | ||
| + | }); | ||
| + | |||
| + | // Optional: kleine Test-Route für DB (zeigt Serverzeit aus MySQL) | ||
| + | app.get('/ | ||
| + | db.query(' | ||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return res.status(500).send(' | ||
| + | } | ||
| + | res.status(200).json(results[0]); | ||
| + | }); | ||
| + | }); | ||
| + | |||
| + | // Server starten | ||
| + | app.listen(port, | ||
| + | console.log(`API läuft auf http:// | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | **Was passiert hier?** | ||
| + | * '' | ||
| + | * Ihre Routen können direkt '' | ||
| + | * Mit ''/ | ||
| + | |||
| + | |||
| + | **Server starten** | ||
| + | Nicht alle Lernenden haben '' | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code bash> | ||
| + | node index.js | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | oder (falls Sie ein dev-Script mit nodemon haben): | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code bash> | ||
| + | npm run dev | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | ===== CRUD-Routen: | ||
| + | Ab hier ersetzen Sie die In-Memory-Liste aus LU16 durch SQL. | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | **Konvention: | ||
| + | </ | ||
| + | |||
| + | ==== READ: alle Posts (GET /api/posts) ==== | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | // READ – alle Posts aus der DB | ||
| + | // Route: GET http:// | ||
| + | app.get('/ | ||
| + | |||
| + | // SQL-Abfrage: | ||
| + | const sql = ` | ||
| + | SELECT id, user_id, title, image_url, description, | ||
| + | FROM posts | ||
| + | `; | ||
| + | |||
| + | // db.query(...) führt die SQL-Abfrage aus | ||
| + | db.query(sql, | ||
| + | |||
| + | // Falls die DB einen Fehler liefert -> 500 | ||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return res.status(500).send(' | ||
| + | } | ||
| + | |||
| + | // results ist ein Array mit Zeilen (Rows) | ||
| + | res.status(200).json(results); | ||
| + | }); | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | === Test in Postman === | ||
| + | * Methode: '' | ||
| + | * URL: '' | ||
| + | * Erwartung: '' | ||
| + | |||
| + | |||
| + | ==== READ: einzelner Post (GET / | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | // READ – einzelner Post nach ID aus der DB | ||
| + | // Route: GET http:// | ||
| + | app.get('/ | ||
| + | |||
| + | // Route-Parameter :id kommt immer als String -> Number(...) macht eine Zahl daraus | ||
| + | const id = Number(req.params.id); | ||
| + | |||
| + | // Einfache Validierung: | ||
| + | if (Number.isNaN(id)) { | ||
| + | return res.status(400).send(' | ||
| + | } | ||
| + | |||
| + | const sql = ` | ||
| + | SELECT id, user_id, title, image_url, description, | ||
| + | FROM posts | ||
| + | WHERE id = ? | ||
| + | `; | ||
| + | |||
| + | // Platzhalter ? wird durch id ersetzt -> Schutz vor SQL-Injection | ||
| + | db.query(sql, | ||
| + | |||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return res.status(500).send(' | ||
| + | } | ||
| + | |||
| + | // Wenn keine Zeile gefunden -> 404 | ||
| + | if (results.length === 0) { | ||
| + | return res.status(404).send(' | ||
| + | } | ||
| + | |||
| + | // results[0] ist der erste (und hier einzige) Treffer | ||
| + | res.status(200).json(results[0]); | ||
| + | }); | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== CREATE: neuen Post erstellen (POST /api/posts) ==== | ||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | // CREATE – neuen Post in der DB anlegen | ||
| + | // Route: POST http:// | ||
| + | app.post('/ | ||
| + | |||
| + | // Daten kommen aus dem Request-Body (Postman: Body -> raw -> JSON) | ||
| + | const user_id = req.body.user_id; | ||
| + | const title = req.body.title; | ||
| + | const image_url = req.body.image_url; | ||
| + | const description = req.body.description; | ||
| + | |||
| + | // Validierung: | ||
| + | if (user_id === undefined || user_id === null || title === undefined || title === null || title === '' | ||
| + | return res.status(400).send(' | ||
| + | } | ||
| + | |||
| + | const sql = ` | ||
| + | INSERT INTO posts (user_id, title, image_url, description, | ||
| + | VALUES (?, ?, ?, ?, 0) | ||
| + | `; | ||
| + | |||
| + | const values = [ | ||
| + | user_id, | ||
| + | title, | ||
| + | image_url || '', | ||
| + | description || '' | ||
| + | ]; | ||
| + | |||
| + | // INSERT ausführen | ||
| + | db.query(sql, | ||
| + | |||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return res.status(500).send(' | ||
| + | } | ||
| + | |||
| + | // insertId kommt von MySQL AUTO_INCREMENT | ||
| + | const newPost = { | ||
| + | id: result.insertId, | ||
| + | user_id: user_id, | ||
| + | title: title, | ||
| + | image_url: image_url || '', | ||
| + | description: | ||
| + | likes: 0 | ||
| + | }; | ||
| + | |||
| + | res.status(201).json(newPost); | ||
| + | }); | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | === Test in Postman === | ||
| + | |||
| + | **Test-Body (Postman → Body → raw → JSON)** | ||
| + | <WRAP box round center 80%> | ||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== UPDATE: Post ändern (PUT / | ||
| + | |||
| + | In LU16b haben Sie einen Post so aktualisiert: | ||
| + | Genau diese Logik übernehmen wir jetzt – der Unterschied ist nur: statt im Array ändern wir jetzt die MySQL-Tabelle '' | ||
| + | |||
| + | |||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | // UPDATE – Post vollständig ersetzen (PUT) | ||
| + | // Route: PUT http:// | ||
| + | app.put('/ | ||
| + | |||
| + | const id = Number(req.params.id); | ||
| + | |||
| + | if (Number.isNaN(id)) { | ||
| + | return res.status(400).send(' | ||
| + | } | ||
| + | |||
| + | // Alle Felder werden erwartet (PUT ersetzt alles) | ||
| + | const user_id = req.body.user_id; | ||
| + | const title = req.body.title; | ||
| + | const image_url = req.body.image_url; | ||
| + | const description = req.body.description; | ||
| + | const likes = req.body.likes; | ||
| + | |||
| + | // Validierung: | ||
| + | // likes kann 0 sein -> deshalb auf undefined prüfen | ||
| + | if ( | ||
| + | user_id === undefined || | ||
| + | title === undefined || | ||
| + | image_url === undefined || | ||
| + | description === undefined || | ||
| + | likes === undefined | ||
| + | ) { | ||
| + | return res.status(400).send(' | ||
| + | } | ||
| + | |||
| + | const sql = ` | ||
| + | UPDATE posts | ||
| + | SET user_id = ?, title = ?, image_url = ?, description = ?, likes = ? | ||
| + | WHERE id = ? | ||
| + | `; | ||
| + | |||
| + | const values = [user_id, title, image_url, description, | ||
| + | |||
| + | db.query(sql, | ||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return res.status(500).send(' | ||
| + | } | ||
| + | |||
| + | if (result.affectedRows === 0) { | ||
| + | return res.status(404).send(' | ||
| + | } | ||
| + | |||
| + | // 200 OK + das " | ||
| + | res.status(200).json({ | ||
| + | id: id, | ||
| + | user_id: user_id, | ||
| + | title: title, | ||
| + | image_url: image_url, | ||
| + | description: | ||
| + | likes: likes | ||
| + | }); | ||
| + | }); | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | === Test in Postman === | ||
| + | |||
| + | **Test-Body** | ||
| + | <WRAP box round center 80%> | ||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | Erwartung: | ||
| + | * Status '' | ||
| + | * JSON-Objekt des aktualisierten Posts (inkl. neuem '' | ||
| + | |||
| + | |||
| + | ==== DELETE: Post löschen (DELETE / | ||
| + | <WRAP box round center 80%> | ||
| + | <code javascript> | ||
| + | // DELETE – Post löschen | ||
| + | app.delete('/ | ||
| + | |||
| + | const id = Number(req.params.id); | ||
| + | |||
| + | if (Number.isNaN(id)) { | ||
| + | return res.status(400).send(' | ||
| + | } | ||
| + | |||
| + | const sql = ' | ||
| + | |||
| + | db.query(sql, | ||
| + | if (err) { | ||
| + | console.error(' | ||
| + | return res.status(500).send(' | ||
| + | } | ||
| + | |||
| + | if (result.affectedRows === 0) { | ||
| + | return res.status(404).send(' | ||
| + | } | ||
| + | |||
| + | // Antwort: 200 + Message | ||
| + | res.status(200).json({ message: `Post mit id=${id} wurde gelöscht.` }); | ||
| + | |||
| + | }); | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | === Test mit Postman === | ||
| + | |||
| + | * Methode: '' | ||
| + | * URL: '' | ||
| + | |||
| + | Erwartung: | ||
| + | * Status '' | ||
| + | * JSON-Objekt des gelöschten Posts | ||
| + | * Danach '' | ||
| + | |||
| + | |||
| + | ===== Typische Fehlerquellen (aus LU16, jetzt noch wichtiger) ===== | ||
| + | * '' | ||
| + | * Ohne '' | ||
| + | * SQL immer mit Platzhaltern ''?'' | ||
| + | * Denken Sie bei '' | ||
| + | * Eingaben ok? → sonst '' | ||
| + | * Datensatz gefunden? → sonst '' | ||
| + | * DB-Fehler? → '' | ||
| + | |||
| + | |||
| + | ===== Transfer auf Ihr Projekt (LB03) ===== | ||
| + | Für Ihr Projekt ersetzen Sie '' | ||
| + | |||
| + | * Ressourcen-Route: | ||
| + | * CRUD: '' | ||
| + | * Zusätzlich: | ||
| + | * mindestens **eine JOIN-Route** (z.B. Serien inkl. Schauspieler: | ||
| + | * mindestens **eine Aggregat-Route** (z.B. Durchschnittsbewertung pro Genre) | ||