Zum Inhalt

Block 05 M295

5.5 Erstellung CRUD Handlers für das eigene Projekt

Bildschirmfoto 2026-03-03 um 14.53.37.png

Bildschirmfoto 2026-03-03 um 15.00.03.png

1. Projekt-Konzept

Diese API verwaltet Lichtpult-Daten (Fixtures) in einer lokalen pult.json. Sie dient als Datenbank-Ersatz für das KSBLab-Projekt.

  • Technik: Node.js, Express.

  • Speicher: pult.json (Feld: lichtpulte).

  • Port: 4010 (Standard).

2. CRUD-Schnittstellen (Übersicht)

Aktion Methode Endpunkt Status
List GET /api/v1/fixtures 200 OK
Read GET /api/v1/fixtures/:id 200 / 404
Create POST /api/v1/fixtures 201 Created
Update PATCH /api/v1/fixtures/:id 200 OK
Delete DELETE /api/v1/fixtures/:id 204 No Content

3. Durchführung & Testing (Postman)

Der Nachweis der Funktionalität wurde erfolgreich über Postman erbracht. Folgende Schritte wurden durchgeführt:

  • Patch-Test: Bestehende Geräte wurden per PATCH-Request modifiziert (z. B. Preis oder Modell geändert). Die API hat die Änderungen korrekt in die pult.json geschrieben.

  • Datenfluss: Ein POST legt neue Fixtures mit automatischer ID-Vergabe an.

  • Validierung: Nach jedem Schreibvorgang wurde über einen GET-Request geprüft, ob die Datei pult.json die aktualisierten Daten korrekt widerspiegelt.

4. Quick Start

  1. Server: node app.js

  2. Test: http://10.27.160.16:4010/api/v1/fixtures in Postman aufrufen.

Code:

const express = require('express');
const fs = require('fs');

const app = express();
app.use(express.json());

// --- Pfad zu deiner JSON-Datei ---
const dataPath = `${__dirname}/pult.json`;

// Hilfsfunktionen
// da pult.json ein Objekt mit dem Feld `lichtpulte` enthält,
// kapseln wir den Zugriff in unseren Funktionen ab
const readData = () => {
    const raw = JSON.parse(fs.readFileSync(dataPath));
    return raw.lichtpulte || [];
};
const writeData = (data) => {
    const wrapper = { lichtpulte: data };
    fs.writeFileSync(dataPath, JSON.stringify(wrapper, null, 2));
};

// 1. Alle Lampen/Geräte abrufen
app.get('/api/v1/fixtures', (req, res) => {
    const fixtures = readData();
    res.status(200).json({ 
        status: 'success', 
        results: fixtures.length, 
        data: { fixtures } 
    });
});

// 2. Ein spezifisches Gerät abrufen (über ID)
app.get('/api/v1/fixtures/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const fixture = readData().find(el => el.id === id);
    if (!fixture) return res.status(404).json({ status: 'fail', message: 'Gerät nicht gefunden' });
    res.status(200).json({ status: 'success', data: { fixture } });
});

// 3. Neues Gerät zum Pult hinzufügen
app.post('/api/v1/fixtures', (req, res) => {
    const fixtures = readData();
    const newId = fixtures.length > 0 ? fixtures[fixtures.length - 1].id + 1 : 1;
    const newFixture = { id: newId, ...req.body };

    fixtures.push(newFixture);
    writeData(fixtures);
    res.status(201).json({ status: 'success', data: { fixture: newFixture } });
});

// 4. Einstellungen eines Geräts ändern (z.B. Dimmer-Wert oder Farbe)
app.patch('/api/v1/fixtures/:id', (req, res) => {
    const id = parseInt(req.params.id);
    let fixtures = readData();
    const index = fixtures.findIndex(el => el.id === id);

    if (index === -1) return res.status(404).json({ status: 'fail', message: 'Gerät nicht gefunden' });

    fixtures[index] = { ...fixtures[index], ...req.body };
    writeData(fixtures);
    res.status(200).json({ status: 'success', data: { fixture: fixtures[index] } });
});

// 5. Gerät aus dem Patch löschen
app.delete('/api/v1/fixtures/:id', (req, res) => {
    const id = parseInt(req.params.id);
    let fixtures = readData();
    const filteredFixtures = fixtures.filter(el => el.id !== id);

    if (fixtures.length === filteredFixtures.length) {
        return res.status(404).json({ status: 'fail', message: 'ID nicht im Patch gefunden' });
    }

    writeData(filteredFixtures);
    res.status(204).json({ status: 'success', data: null });
});

// Port wählbar über Umgebungsvariable, Standard 4010
const PORT = process.env.PORT || 4010;
app.listen(PORT, '0.0.0.0', () => {
    console.log(`Lichtpult-Konsole läuft auf Port ${PORT}...`);
});

1. Initialisierung & Middleware

Dieser Teil bereitet den Server vor und lädt die notwendigen Module.

  • Express & FS: Importiert das Framework für die API und das Dateisystem-Modul für den Zugriff auf die pult.json.

  • JSON Middleware: app.use(express.json()) stellt sicher, dass der Server Daten im JSON-Format empfangen kann.

JavaScript

const express = require('express');
const fs = require('fs');

const app = express();
app.use(express.json());

const dataPath = `${__dirname}/pult.json`;

2. Datenverwaltung (Helferfunktionen)

Hier wird die Logik für den Zugriff auf die "Datenbank" (die JSON-Datei) gekapselt.

  • readData: Liest die Datei aus, wandelt sie in ein Objekt um und gibt das Array lichtpulte zurück.

  • writeData: Nimmt neue Daten entgegen, verpackt sie wieder in das Objekt-Format und speichert sie formatiert in die Datei.

JavaScript

const readData = () => {
    const raw = JSON.parse(fs.readFileSync(dataPath));
    return raw.lichtpulte || [];
};
const writeData = (data) => {
    const wrapper = { lichtpulte: data };
    fs.writeFileSync(dataPath, JSON.stringify(wrapper, null, 2));
};

3. READ (Daten abrufen)

Diese Endpunkte liefern Informationen über die vorhandenen Lampen.

  • GET all: Gibt alle Geräte zurück.

  • GET one: Sucht ein spezifisches Gerät anhand der ID in der URL (:id).

JavaScript

// Alle Geräte abrufen
app.get('/api/v1/fixtures', (req, res) => {
    const fixtures = readData();
    res.status(200).json({ 
        status: 'success', 
        results: fixtures.length, 
        data: { fixtures } 
    });
});

// Ein spezifisches Gerät abrufen
app.get('/api/v1/fixtures/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const fixture = readData().find(el => el.id === id);
    if (!fixture) return res.status(404).json({ status: 'fail', message: 'Gerät nicht gefunden' });
    res.status(200).json({ status: 'success', data: { fixture } });
});

4. CREATE (Hinzufügen)

Erstellt ein neues Gerät im Lichtpult-System.

  • Logik: Berechnet automatisch die nächste ID basierend auf dem letzten Element und speichert das neue Objekt ab.

JavaScript

app.post('/api/v1/fixtures', (req, res) => {
    const fixtures = readData();
    const newId = fixtures.length > 0 ? fixtures[fixtures.length - 1].id + 1 : 1;
    const newFixture = { id: newId, ...req.body };

    fixtures.push(newFixture);
    writeData(fixtures);
    res.status(201).json({ status: 'success', data: { fixture: newFixture } });
});

5. PATCH (Aktualisieren)

Ändert gezielt Werte eines bestehenden Geräts.

  • Logik: Sucht das Gerät per ID. Mit dem Spread-Operator (...) werden nur die Felder aktualisiert, die du in Postman mitschickst (z. B. nur der dimmer), während der Rest gleich bleibt.

JavaScript

app.patch('/api/v1/fixtures/:id', (req, res) => {
    const id = parseInt(req.params.id);
    let fixtures = readData();
    const index = fixtures.findIndex(el => el.id === id);

    if (index === -1) return res.status(404).json({ status: 'fail', message: 'Gerät nicht gefunden' });

    fixtures[index] = { ...fixtures[index], ...req.body };
    writeData(fixtures);
    res.status(200).json({ status: 'success', data: { fixture: fixtures[index] } });
});

6. DELETE (Löschen)

Entfernt ein Gerät aus der Konfiguration.

  • Logik: Verwendet .filter(), um eine neue Liste zu erstellen, die alle Geräte außer das mit der angegebenen ID enthält.

JavaScript

app.delete('/api/v1/fixtures/:id', (req, res) => {
    const id = parseInt(req.params.id);
    let fixtures = readData();
    const filteredFixtures = fixtures.filter(el => el.id !== id);

    if (fixtures.length === filteredFixtures.length) {
        return res.status(404).json({ status: 'fail', message: 'ID nicht im Patch gefunden' });
    }

    writeData(filteredFixtures);
    res.status(204).json({ status: 'success', data: null });
});

7. Server-Konfiguration

Legt fest, auf welcher Adresse und auf welchem Port der Pi auf Anfragen wartet.

JavaScript

const PORT = process.env.PORT || 4010;
app.listen(PORT, '0.0.0.0', () => {
    console.log(`Lichtpult-Konsole läuft auf Port ${PORT}...`);
});

POST:

Es geht :)

Dokumentation: MongoDB-Integration

Die Anbindung der Datenbank erfolgt über Mongoose, einen Object Data Manager (ODM) für Node.js. Dies ermöglicht eine strukturierte Arbeit mit Dokumenten in MongoDB durch vordefinierte Schemata.

1. Die Verbindungslogik (Explanation)

Der Verbindungsaufbau nutzt das MongoDB Atlas-Protokoll (mongodb+srv).

  • Asynchronität: Da der Verbindungsaufbau Zeit benötigt, arbeitet Mongoose mit Promises.

  • Erfolgsbestätigung: Mit .then() wird eine Bestätigung in der Konsole ausgegeben, sobald der Handshake mit dem Cluster abgeschlossen ist.

  • Fehlerbehandlung: Mit .catch() werden Probleme (wie falsche Zugangsdaten oder Netzwerkfehler) abgefangen, damit die App nicht ohne Fehlermeldung abstürzt.

2. Der Verbindungscode (Implementation)

JavaScript

// Verbindung zur MongoDB herstellen
mongoose
  .connect('mongodb+srv://tlangenauer_db_user:<passwort>@cluster0.u4yfkok.mongodb.net/?appName=Cluster0')
  .then(() => {
    console.log('DB Connection successful!');
  })
  .catch(err => {
    console.error('DB Connection failed:', err);
  });

3. Die Datenstruktur (Schema)

Bevor Daten fliessen können, muss die Anwendung wissen, welche Felder ein Dokument besitzt.

  • Schema-Definition: Legt fest, dass ein „Benutzer“ immer einen Namen (String), ein Alter (Number) und eine E-Mail (String) hat.

  • Modell-Instanziierung: Das Modell Benutzer dient als Schnittstelle, um später Befehle wie .find() oder .create() auszuführen.

4. Der Modell-Code

JavaScript

// MongoDB-Modell definieren
const Benutzer = mongoose.model(
  'Benutzer', // Name des Modells
  {
    name: String,
    alter: Number,
    email: String,
  },
  'Benutzer' // Expliziter Name der Collection in der DB
);

Eigenes Projekt Equiptmentvermietung:

1. Projekt-Idee & Zielsetzung

Das Projekt umfasst die Entwicklung einer webbasierten Bestandsverwaltung für technisches Equipment. Ziel ist es, den manuellen Aufwand bei der Verleihung und Schadensmeldung zu minimieren. Die Applikation dient als zentrale Schnittstelle zwischen Lager-Mitarbeitern (physische Inventur) und dem Büro (Verwaltung/Einkauf).

  • Zentraler Nutzen: Echtzeit-Übersicht über den Zustand und Standort von teurem Equipment (z.B. Kameras, Audio-Equipment).

  • Fokus: Schnelle Reaktionszeiten durch ein intuitives User Interface und lückenlose Nachverfolgbarkeit durch systemseitiges Logging.


2. Vollständigkeit der Kernfunktionen (CRUD)

Die Applikation bildet den vollständigen Lebenszyklus eines Inventar-Objekts ab:

  • Create (Anlegen): Über das Modal im Dashboard können neue Artikel mit Parametern wie Name, Kategorie und initialem Bestand in die MongoDB geschrieben werden.

  • Read (Anzeigen): Die Haupt-Tabelle rendert alle Dokumente der Datenbank-Collection dynamisch via Template-Engine (EJS/Pug/Handlebars).

  • Update (Bearbeiten): Mitarbeiter können Stammdaten ändern oder – im Sinne des Schadensmanagements – den Status auf "Defekt" setzen inklusive Kommentarfunktion.

  • Delete (Löschen): Veraltetes oder verkauftes Equipment kann über den Delete-Button permanent aus der Datenbank entfernt werden.


3. Benutzerfreundlichkeit & Visuelle Klarheit

Das Design folgt dem Prinzip "Function over Form", um im stressigen Arbeitsalltag (Lager) Fehlbedienungen zu vermeiden:

  • Hierarchisches Layout: Wichtige Kennzahlen wie der Status ("Defekt") werden visuell hervorgehoben (z.B. durch Signalfarben oder Ausrufezeichen).

  • Intuitive Navigation: Die klare Trennung zwischen globaler Übersicht (Dashboard) und spezifischer Bearbeitung (Detailansicht) verhindert Informationsüberlastung.

  • Barrierefreie Bedienung: Große Klickflächen für Icons und Buttons erleichtern die Nutzung auf Tablets oder mobilen Endgeräten im Lagerbereich.


4. Technische Umsetzbarkeit (Zeitplan: ca. 8 Lektionen)

Das Projekt ist modular aufgebaut, was eine Umsetzung innerhalb des Zeitrahmens realistisch macht:

Lektion Fokus Meilenstein
1 - 2 Backend-Setup Express-Server steht, Verbindung zur MongoDB Atlas ist stabil.
3 - 4 Datenmodell & CRUD Schema-Definition für Equipment; Routen für GET und POST funktionieren.
5 - 6 Frontend & Templates Integration der Template-Engine; Dynamisches Rendern der Tabelle.
7 Auth & Logging Implementierung von bcrypt für Passwörter und Mongoose Middleware für Logs.
8 Testing & Polishing Fehlerbehandlung (Error Handling) und finales UI-Tuning.

5. Sicherheitskonzept & Logging (Technischer Deep-Dive)

Ein wesentlicher Bestandteil der professionellen Umsetzung ist der Schutz der Daten und die Revisionssicherheit:

  • Passwort-Hashing: Passwörter werden niemals im Klartext gespeichert. Wir nutzen die Library bcrypt, um Passwörter mit einem Salt zu hashen.

  • Session-Management: Zugriff auf das Inventar ist nur für authentifizierte User über geschützte Routen (Middleware) möglich.

  • Automatisierte Historie: Jede Statusänderung triggert eine Funktion im Backend, die einen neuen Eintrag im Log-Schema erstellt. Dies speichert:

  • Timestamp: Wann wurde die Änderung vorgenommen?

  • UserID: Welcher Mitarbeiter war verantwortlich?

  • Change: Welcher Wert wurde von X auf Y geändert?


6. Datenstruktur (Beispiel-Schema)

Um die technische Tiefe zu verdeutlichen, basiert das Projekt auf folgendem Datenmodell:

  • Equipment-Collection:

  • name: String

  • category: String

  • status: String (Enum: ['Verfügbar', 'Verliehen', 'Defekt'])

  • stock: Number

  • comments: Array of Strings (für Schadensberichte)

Wireframe:

User ansicht:

Bildschirmfoto 2026-03-04 um 14.29.21.png

User-Ansicht

  • Fokus: Nur das Ausleihen von Equipment.

  • Funktionen: Jedes Produkt (z. B. Beamer oder Washlight) hat lediglich einen Button mit der Beschriftung „Ausleihen“.

  • Einschränkung: Es gibt keine Möglichkeiten, das Inventar zu bearbeiten oder neue Produkte hinzuzufügen.

Admin ansicht

Bildschirmfoto 2026-03-04 um 14.28.42.png

Admin-Ansicht

  • Fokus: Verwaltung und Administration des Bestands.

  • Zusätzliche Funktionen:

  • Ein blauer Button „+ Produkt hinzufügen“ (markiert als Post).

  • Jedes Produkt hat Optionen zum Bearbeiten (Edit, markiert als Patch) und Löschen (Del, markiert als Delete).

  • Technische Hinweise: Schwarze Pfeile markieren die entsprechenden HTTP-Methoden (Get, Post, Patch, Delete) für die Entwicklung.