Files
uni/slides/dhbw-2026/04_nodejs_advanced.md
T
libretech 351e3dfc23 branch-coexist: 3 neue decks 223015b-2026 / 223015c-2026 / dhbw-2026 daneben main
per user-request einen branch der main-content UND rewrite-content trägt — saubere separierung statt symlink/merge-conflict.

struktur:
- slides/<course>/ → main-content restored, eigene assets (kein new-demo-leak)
- slides/<course>-2026/ → rewrite-content, eigene assets (komplett kopiert + new demos)

Makefile: COURSES += 223015b-2026 223015c-2026 dhbw-2026 mit eigenen NAME/KAPITEL/DEPLOY/KLAUSUR-konfig. Klausur für b+c=1, dhbw=leer. Deploy-paths /hdm/223015b-2026 etc.

make build durchlaufen für alle 6 decks, 0 fehler.

dedup: 223015c+dhbw assets identisch in beiden ordnern (kein neu-demos auf b ergänzt). 223015b 86 → 120 files in -2026 (= +34 neue eroeffner/usb-c-drei-achsen/container-vs-codec/etc).
2026-05-14 21:54:15 +02:00

8.3 KiB
Raw Blame History

marp, theme, paginate, backgroundColor, header, footer
marp theme paginate backgroundColor header footer
true gaia true Web Engineering – DHBW Stuttgart Michael Czechowski – SoSe 2026
<style> :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; } section.invert { --color-foreground: #fff; } section { font-size: 1.4rem; } h1 { color: #a02060; } section.invert h1 { color: #fff; } h2 { color: #1f2937; } pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; } pre code { background: transparent; color: inherit; } code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; } a { color: var(--color-highlight); } </style>

Kapitel 4 — Node.js Advanced

async · Files · Database · Auth · Errors


Asynchron + Promises


Synchron vs. Asynchron

Synchron (blockierend):

const data = readFileSync('huge.txt');  // wartet
console.log('weiter');                    // erst danach

Asynchron (non-blocking):

const data = await readFile('huge.txt'); // wartet ohne blockieren
console.log('weiter');                    // erst danach

Node's Stärke: non-blocking I/O für viele parallele Operationen.


Promise — Anatomie

const promise = fetch('https://api.example.com');

// then-style (alt)
promise
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

// async/await (modern)
try {
  const res = await fetch('https://api.example.com');
  const data = await res.json();
  console.log(data);
} catch (err) {
  console.error(err);
}

Sequenziell vs. Parallel

// SEQUENZIELL — langsam (warten + warten + warten)
const a = await fetch('/api/a');
const b = await fetch('/api/b');
const c = await fetch('/api/c');
// Gesamtzeit: a + b + c

// PARALLEL — schnell (alle gleichzeitig starten)
const [a, b, c] = await Promise.all([
  fetch('/api/a'),
  fetch('/api/b'),
  fetch('/api/c'),
]);
// Gesamtzeit: max(a, b, c)

Promise-Methoden

Methode Was
Promise.all([…]) alle erfolgreich ODER ein Fehler
Promise.allSettled([…]) alle, Resultat = { status, value/reason }
Promise.race([…]) erstes Resultat gewinnt (auch Fehler)
Promise.any([…]) erstes erfolgreiches, ignore Fehler
const fastest = await Promise.race([
  fetch('/api'),
  new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000))
]);

Filesystem


fs/promises — Files lesen + schreiben

import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';

// Lesen
const text = await readFile('input.txt', 'utf-8');
const buf = await readFile('image.png');         // Buffer

// Schreiben
await writeFile('output.txt', 'Hallo Welt');
await writeFile('data.json', JSON.stringify({ x: 1 }, null, 2));

// Verzeichnis lesen
const files = await readdir('./uploads');

// Verzeichnis anlegen
await mkdir('./tmp', { recursive: true });

Streams für große Dateien

import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

// 10 GB Logfile ohne RAM-Explosion verarbeiten
await pipeline(
  createReadStream('huge.log'),
  // ... transform-stream ...
  createWriteStream('output.log')
);

Streams = Daten stückweise verarbeiten, nicht alles in RAM.


Datenbank


Welche DB für DHBW-Projekte?

DB Wann
SQLite klein, embedded, eine Datei. Lokal/dev/demo.
PostgreSQL Standard für „echte" Apps. Open Source, mächtig.
MongoDB NoSQL, dokumentenorientiert. Selten erste Wahl.
Redis Cache, Queue, schnell.

Für Prüfungsleistung: SQLite (kein Setup) oder Postgres (production-realistisch).


SQLite mit better-sqlite3

import Database from 'better-sqlite3';
const db = new Database('app.db');

db.prepare(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`).run();

// Insert
const insert = db.prepare('INSERT INTO users (name) VALUES (?)');
insert.run('Ada');

// Query
const users = db.prepare('SELECT * FROM users').all();
const ada = db.prepare('SELECT * FROM users WHERE id = ?').get(1);

ORM / Query-Builder

Für komplexere Apps: ORM statt Raw-SQL.

Tool Typ
Drizzle TypeScript-first, modern, lightweight
Prisma umfangreich, eigenes Schema
Knex Query-Builder, kein ORM
TypeORM klassisches ORM
// Drizzle Beispiel
const result = await db.select().from(users).where(eq(users.id, 1));

CRUD-Pattern mit Express + SQLite

app.get('/users', (req, res) => {
  const users = db.prepare('SELECT * FROM users').all();
  res.json(users);
});

app.get('/users/:id', (req, res) => {
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.params.id);
  user ? res.json(user) : res.sendStatus(404);
});

app.post('/users', (req, res) => {
  const { name } = req.body;
  const result = db.prepare('INSERT INTO users (name) VALUES (?)').run(name);
  res.status(201).json({ id: result.lastInsertRowid, name });
});

Authentifizierung


Auth-Strategien

Strategie Wann
Session-Cookie klassische Web-App, Server-Side Render
JWT (JSON Web Token) API + SPA, stateless
OAuth „Mit Google einloggen", Drittpartei-Login
Magic Link Email statt Passwort, modern
Passkeys passwordless, FIDO2, Zukunft

Für DHBW-Projekt: Session-Cookie (express-session) ODER JWT.


Passwörter — niemals plaintext

import bcrypt from 'bcrypt';

// Registrieren
const hashed = await bcrypt.hash('mein-passwort', 12);
db.prepare('INSERT INTO users (email, password) VALUES (?, ?)').run(email, hashed);

// Login
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
const valid = await bcrypt.compare(password, user.password);

bcrypt oder argon2 für Passwort-Hashing. Niemals MD5/SHA1.


JWT — kurzer Überblick

import jwt from 'jsonwebtoken';

// Signieren (beim Login)
const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '1d' }
);
res.cookie('token', token, { httpOnly: true, secure: true });

// Verifizieren (Middleware)
const token = req.cookies.token;
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;

httpOnly + secure + sameSite — XSS/CSRF-Schutz.


Error-Handling


try/catch + Error-Klassen

class NotFoundError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NotFoundError';
    this.status = 404;
  }
}

try {
  const user = await getUser(id);
  if (!user) throw new NotFoundError(`User ${id} not found`);
  res.json(user);
} catch (err) {
  if (err instanceof NotFoundError) return res.status(404).json({ error: err.message });
  console.error(err);
  res.sendStatus(500);
}

Globaler Express-Error-Handler

// Am Ende aller Routes!
app.use((err, req, res, next) => {
  console.error(err);
  
  if (err.status) {
    return res.status(err.status).json({ error: err.message });
  }
  
  res.status(500).json({ error: 'Internal Server Error' });
});

4 Argumente: (err, req, res, next) → Express erkennt Error-Handler.


Selbstlernen — REST-API mit DB

Erweitert eure Kap-3-API:

  1. SQLite-DB mit better-sqlite3
  2. CRUD mit DB statt In-Memory-Array
  3. Validierung (z.B. mit zod) für POST-Body
  4. Auth: einfache Login-Route mit bcrypt + JWT
  5. Global Error-Handler für saubere Responses

Bonus: Async-Race-Condition simulieren + Promise.all nutzen.


Zusammenfassung

Heute gelernt:

  • async/await + Promise sauber anwenden, sequenziell vs. parallel
  • fs/promises + Streams für Dateien
  • SQLite + Prepared Statements als pragmatische DB
  • bcrypt für Passwörter, JWT als Token-Pattern
  • Error-Klassen + Globaler Handler in Express

Nächste Stunde: Testing — Vitest, TDD, Integration, E2E.