Skip to content

Kapitel 12: Fortgeschrittene Praxis

🎯 Lernziele

In diesem Kapitel lernen Sie:

  • ✅ TodoList-Backend-API vollständig entwickeln
  • ✅ CRUD-Operationen implementieren (Create, Read, Update, Delete)
  • ✅ Modularisierung (Code aufteilen)
  • ✅ Fehlerbehandlung
  • ✅ Node.js mit MySQL-Datenbank verbinden
  • mysql2-Paket verwenden
  • ✅ TodoList mit MySQL-Datenbank verbinden

Praxis 4: TodoList Backend-API (Vollständige API-Entwicklung)

📝 Anforderungsanalyse

Ziel: Eine vollständige REST-API für eine TodoList-Anwendung erstellen.

Kernfunktionalitäten (CRUD):

  1. Alle Todos abrufen (GET)
  2. Einzelnes Todo abrufen (GET)
  3. Neues Todo erstellen (POST)
  4. Todo aktualisieren (PUT)
  5. Todo löschen (DELETE)

🏗️ Projektstruktur

todo-api/
├── server.js           # Hauptserver-Datei
├── package.json
├── .env               # Umgebungsvariablen
├── data/
│   └── todos.json     # "Datenbank" (JSON-Datei)
└── routes/
    └── todos.js       # (Optional) Routen-Modul

📦 package.json erstellen

bash
mkdir todo-api
cd todo-api
npm init -y
npm install express uuid

package.json:

json
{
  "name": "todo-api",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}

💻 Kernimplementierung

server.js:

javascript
const express = require('express');
const fs = require('fs').promises;
const path = require('path');
const { v4: uuidv4 } = require('uuid');

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());

// "Datenbank"-Pfad
const DB_PFAD = path.join(__dirname, 'data', 'todos.json');

// Hilfsfunktion: Todos aus Datei lesen
async function todosLesen() {
  try {
    const daten = await fs.readFile(DB_PFAD, 'utf8');
    return JSON.parse(daten);
  } catch (err) {
    // Datei existiert noch nicht → leeres Array zurückgeben
    return [];
  }
}

// Hilfsfunktion: Todos in Datei schreiben
async function todosSchreiben(todos) {
  await fs.writeFile(DB_PFAD, JSON.stringify(todos, null, 2), 'utf8');
}

// === ROUTES ===

// 1. Alle Todos abrufen (GET)
app.get('/api/todos', async (req, res) => {
  try {
    const todos = await todosLesen();
    res.json({ success: true, data: todos });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Lesen der Todos' });
  }
});

// 2. Einzelnes Todo abrufen (GET)
app.get('/api/todos/:id', async (req, res) => {
  try {
    const todos = await todosLesen();
    const todo = todos.find(t => t.id === req.params.id);
    
    if (!todo) {
      return res.status(404).json({ success: false, error: 'Todo nicht gefunden' });
    }
    
    res.json({ success: true, data: todo });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Serverfehler' });
  }
});

// 3. Neues Todo erstellen (POST)
app.post('/api/todos', async (req, res) => {
  try {
    const { title, description } = req.body;
    
    // Validierung
    if (!title) {
      return res.status(400).json({ success: false, error: 'Titel ist erforderlich' });
    }
    
    const newTodo = {
      id: uuidv4(),
      title,
      description: description || '',
      completed: false,
      createdAt: new Date().toISOString()
    };
    
    const todos = await todosLesen();
    todos.push(newTodo);
    await todosSchreiben(todos);
    
    res.status(201).json({ success: true, data: newTodo });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Erstellen des Todos' });
  }
});

// 4. Todo aktualisieren (PUT)
app.put('/api/todos/:id', async (req, res) => {
  try {
    const { title, description, completed } = req.body;
    const todos = await todosLesen();
    const todoIndex = todos.findIndex(t => t.id === req.params.id);
    
    if (todoIndex === -1) {
      return res.status(404).json({ success: false, error: 'Todo nicht gefunden' });
    }
    
    // Todo aktualisieren
    if (title !== undefined) todos[todoIndex].title = title;
    if (description !== undefined) todos[todoIndex].description = description;
    if (completed !== undefined) todos[todoIndex].completed = completed;
    
    await todosSchreiben(todos);
    
    res.json({ success: true, data: todos[todoIndex] });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' });
  }
});

// 5. Todo löschen (DELETE)
app.delete('/api/todos/:id', async (req, res) => {
  try {
    const todos = await todosLesen();
    const todoIndex = todos.findIndex(t => t.id === req.params.id);
    
    if (todoIndex === -1) {
      return res.status(404).json({ success: false, error: 'Todo nicht gefunden' });
    }
    
    // Todo löschen
    const [gelöschtesTodo] = todos.splice(todoIndex, 1);
    await todosSchreiben(todos);
    
    res.json({ success: true, message: 'Todo gelöscht', data: gelöschtesTodo });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Löschen' });
  }
});

// Server starten
app.listen(PORT, () => {
  console.log(`🚀 Todo-API läuft auf http://localhost:${PORT}`);
  console.log('\nVerfügbare Endpunkte:');
  console.log('  GET    /api/todos');
  console.log('  GET    /api/todos/:id');
  console.log('  POST   /api/todos');
  console.log('  PUT    /api/todos/:id');
  console.log('  DELETE /api/todos/:id');
});

🧪 API testen

Mit curl:

bash
# 1. Alle Todos abrufen
curl http://localhost:3000/api/todos

# 2. Neues Todo erstellen
curl -X POST http://localhost:3000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Einkaufen","description":"Milch und Brot kaufen"}'

# 3. Todo aktualisieren
curl -X PUT http://localhost:3000/api/todos/<TODO_ID> \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

# 4. Todo löschen
curl -X DELETE http://localhost:3000/api/todos/<TODO_ID>

Mit Postman:

  1. Öffnen Sie Postman
  2. Wählen Sie die HTTP-Methode (GET, POST, PUT, DELETE)
  3. Geben Sie die URL ein (z.B. http://localhost:3000/api/todos)
  4. Bei POST/PUT: Body → rawJSON{ "title": "Test" }
  5. Klicken Sie auf Send

Praxis 5: Node.js mit Datenbank verbinden (MySQL)

📝 Anforderungsanalyse

Ziel: TodoList-Daten in einer MySQL-Datenbank speichern (statt JSON-Datei).


🗄️ Datenbank-Grundlagen: MySQL

MySQL installieren

Windows:

  1. Laden Sie MySQL von mysql.com herunter
  2. Folgen Sie dem Installations-Assistenten

macOS:

bash
brew install mysql
brew services start mysql

Linux (Ubuntu/Debian):

bash
sudo apt update
sudo apt install mysql-server
sudo systemctl start mysql

Datenbank und Tabelle erstellen

sql
-- Bei MySQL anmelden
mysql -u root -p

-- Datenbank erstellen
CREATE DATABASE todo_app;
USE todo_app;

-- Tabelle erstellen
CREATE TABLE todos (
  id VARCHAR(36) PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  description TEXT,
  completed BOOLEAN DEFAULT FALSE,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Testdaten einfügen
INSERT INTO todos (id, title, description) VALUES
  (UUID(), 'Erstes Todo', 'Dies ist ein Test'),
  (UUID(), 'Zweites Todo', 'Noch ein Test');

-- Alle Todos anzeigen
SELECT * FROM todos;

📦 mysql2-Paket installieren

bash
npm install mysql2

💻 Kernimplementierung (mit MySQL)

server-mysql.js:

javascript
const express = require('express');
const mysql = require('mysql2/promise');
const { v4: uuidv4 } = require('uuid');
require('dotenv').config();

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());

// Datenbank-Verbindungspool erstellen
const pool = mysql.createPool({
  host: process.env.DB_HOST || 'localhost',
  user: process.env.DB_USER || 'root',
  password: process.env.DB_PASSWORD || '',
  database: process.env.DB_NAME || 'todo_app',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

// Hilfsfunktion: Pool verbindung testen
async function dbVerbindungTesten() {
  try {
    const connection = await pool.getConnection();
    console.log('✅ Datenbankverbindung erfolgreich!');
    connection.release();
  } catch (err) {
    console.error('❌ Datenbankverbindung fehlgeschlagen:', err.message);
    process.exit(1);
  }
}

// === ROUTES ===

// 1. Alle Todos abrufen (GET)
app.get('/api/todos', async (req, res) => {
  try {
    const [rows] = await pool.execute('SELECT * FROM todos ORDER BY created_at DESC');
    res.json({ success: true, data: rows });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Todos' });
  }
});

// 2. Einzelnes Todo abrufen (GET)
app.get('/api/todos/:id', async (req, res) => {
  try {
    const [rows] = await pool.execute('SELECT * FROM todos WHERE id = ?', [req.params.id]);
    
    if (rows.length === 0) {
      return res.status(404).json({ success: false, error: 'Todo nicht gefunden' });
    }
    
    res.json({ success: true, data: rows[0] });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Serverfehler' });
  }
});

// 3. Neues Todo erstellen (POST)
app.post('/api/todos', async (req, res) => {
  try {
    const { title, description } = req.body;
    
    // Validierung
    if (!title) {
      return res.status(400).json({ success: false, error: 'Titel ist erforderlich' });
    }
    
    const id = uuidv4();
    await pool.execute(
      'INSERT INTO todos (id, title, description) VALUES (?, ?, ?)',
      [id, title, description || '']
    );
    
    // Erstelltes Todo abrufen
    const [rows] = await pool.execute('SELECT * FROM todos WHERE id = ?', [id]);
    
    res.status(201).json({ success: true, data: rows[0] });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Erstellen des Todos' });
  }
});

// 4. Todo aktualisieren (PUT)
app.put('/api/todos/:id', async (req, res) => {
  try {
    const { title, description, completed } = req.body;
    
    // Prüfen, ob Todo existiert
    const [existingRows] = await pool.execute('SELECT * FROM todos WHERE id = ?', [req.params.id]);
    
    if (existingRows.length === 0) {
      return res.status(404).json({ success: false, error: 'Todo nicht gefunden' });
    }
    
    // Dynamisches Update
    const updates = [];
    const values = [];
    
    if (title !== undefined) {
      updates.push('title = ?');
      values.push(title);
    }
    if (description !== undefined) {
      updates.push('description = ?');
      values.push(description);
    }
    if (completed !== undefined) {
      updates.push('completed = ?');
      values.push(completed);
    }
    
    if (updates.length > 0) {
      values.push(req.params.id);
      await pool.execute(`UPDATE todos SET ${updates.join(', ')} WHERE id = ?`, values);
    }
    
    // Aktualisiertes Todo abrufen
    const [rows] = await pool.execute('SELECT * FROM todos WHERE id = ?', [req.params.id]);
    
    res.json({ success: true, data: rows[0] });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' });
  }
});

// 5. Todo löschen (DELETE)
app.delete('/api/todos/:id', async (req, res) => {
  try {
    // Todo vor dem Löschen abrufen
    const [rows] = await pool.execute('SELECT * FROM todos WHERE id = ?', [req.params.id]);
    
    if (rows.length === 0) {
      return res.status(404).json({ success: false, error: 'Todo nicht gefunden' });
    }
    
    // Todo löschen
    await pool.execute('DELETE FROM todos WHERE id = ?', [req.params.id]);
    
    res.json({ success: true, message: 'Todo gelöscht', data: rows[0] });
  } catch (err) {
    res.status(500).json({ success: false, error: 'Fehler beim Löschen' });
  }
});

// Server starten
app.listen(PORT, async () => {
  await dbVerbindungTesten();
  console.log(`🚀 Todo-API (MySQL) läuft auf http://localhost:${PORT}`);
  console.log('\nVerfügbare Endpunkte:');
  console.log('  GET    /api/todos');
  console.log('  GET    /api/todos/:id');
  console.log('  POST   /api/todos');
  console.log('  PUT    /api/todos/:id');
  console.log('  DELETE /api/todos/:id');
});

🔐 .env-Datei erstellen

bash
# .env
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=dein_passwort
DB_NAME=todo_app

Wichtig: Installieren Sie dotenv:

bash
npm install dotenv

📝 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • ✅ Vollständige TodoList-Backend-API mit Express entwickeln
  • ✅ CRUD-Operationen (Create, Read, Update, Delete)
  • ✅ Fehlerbehandlung implementieren
  • ✅ MySQL-Datenbank mit Node.js verbinden
  • mysql2-Paket verwenden
  • ✅ SQL-Abfragen mit pool.execute() ausführen

🎯 Nächste Schritte

Im nächsten Kapitel werden wir:

  • Häufige Fehler von Node.js-Anfängern analysieren
  • Fehlerbehebungs-Techniken lernen
  • Debugging-Methoden

📚 Weiterführende Ressourcen


🎉 Kapitel 12 abgeschlossen! Weiter zu Kapitel 13: Häufige Fehler

Frei für alle Anfänger