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).
This commit is contained in:
2026-05-14 21:54:15 +02:00
parent 6209f9f3a0
commit 351e3dfc23
467 changed files with 32391 additions and 4701 deletions
+244 -328
View File
@@ -8,373 +8,289 @@ footer: "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); }
: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;
}
section.invert h2 {
color: #f48fb1;
}
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>
<!-- _class: lead -->
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Kapitel 4 — Node.js Advanced
## async · Files · Database · Auth · Errors
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 5 — Express API, CRUD und Middlewares
---
<!-- _class: lead -->
# Ressourcen zum Selbstlernen
# Asynchron + Promises
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
---
# Synchron vs. Asynchron
# Inhalt
**Synchron** (blockierend):
```javascript
const data = readFileSync('huge.txt'); // wartet
console.log('weiter'); // erst danach
```
**Asynchron** (non-blocking):
```javascript
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.
1. Node.js — Fun Facts & alternative Runtimes
2. `process` — Signal-Handling, Graceful Shutdown
3. Express — Setup, Routing
4. Middlewares (eigene + built-in)
5. CRUD-Endpunkte (Create, Read, Update, Delete)
6. Error Handling
7. WebSockets
---
# Promise — Anatomie
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# Node.js – Advanced
---
# node.js – Fun Facts
- Entwickelt von Ryan Dahl in 2009
- Erste serverseitige Laufzeitumgebung in JavaScript (C++)
- Inspiriert von Nginx (Non-blocking I/O)
**Alternativen heute:**
- **Deno** (2020): Rust, Tokio
- **Bun** (2022): Zig
---
# process – Prozess-Steuerung
```javascript
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
```javascript
// 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 |
```javascript
const fastest = await Promise.race([
fetch('/api'),
new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000))
]);
```
---
<!-- _class: lead -->
# Filesystem
---
# fs/promises — Files lesen + schreiben
```javascript
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
```javascript
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.
---
<!-- _class: lead -->
# 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`
```javascript
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 |
```typescript
// Drizzle Beispiel
const result = await db.select().from(users).where(eq(users.id, 1));
```
---
# CRUD-Pattern mit Express + SQLite
```javascript
app.get('/users', (req, res) => {
const users = db.prepare('SELECT * FROM users').all();
res.json(users);
process.on("SIGINT", () => {
console.log("Received SIGINT. Closing server...");
server.close(() => {
console.log("Server closed.");
process.exit(0);
});
});
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);
});
// Signale
process.exit(0); // Erfolgreich
process.exit(1); // Fehler
```
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 });
---
# node.js Module System
```javascript
// ES Modules (modern, "type": "module")
import express from "express";
import { port } from "./src/config/constants.js";
import { readFileSync } from "node:fs";
```
```javascript
// CommonJS (legacy)
const express = require("express");
const fs = require("fs");
```
→ Neue Projekte: ESM. Built-ins immer mit `node:`-Präfix.
---
# HTTP Request mit node-fetch
```javascript
// Node.js 18+ hat fetch eingebaut
const res = await fetch('https://api.example.com/data');
const data = await res.json();
// Ältere Versionen: node-fetch
import fetch from 'node-fetch';
```
---
# Web Server, Builder, Bundler
```bash
# Boilerplates erstellen
npm create vite@latest my-app -- --template react
npm create svelte@latest
npm create astro@latest
# nuxt
npm install nuxt
npm run dev
```
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/js-frameworks-overview.png)
---
# Express – Grundlagen
```javascript
import express from 'express';
const app = express();
app.listen(port, () => {
console.log(`Server läuft auf Port ${port}`);
});
```
---
<!-- _class: lead -->
# 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*
# Express – Middleware
```javascript
import bcrypt from 'bcrypt';
app.use(express.json());
// Registrieren
const hashed = await bcrypt.hash('mein-passwort', 12);
db.prepare('INSERT INTO users (email, password) VALUES (?, ?)').run(email, hashed);
app.use('/api', (req, res, next) => {
console.log('Time:', Date.now());
next();
});
// 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
```javascript
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.
---
<!-- _class: lead -->
# Error-Handling
---
# try/catch + Error-Klassen
```javascript
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
```javascript
// 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' });
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});
```
4 Argumente: `(err, req, res, next)` → Express erkennt Error-Handler.
---
# Express – CRUD Routes
```javascript
app.get('/api/:key', async (req, res) => {
const context = cache.get(req.params.key);
res.json({ context });
});
app.post('/api/:key', async (req, res) => {
const payload = req.body;
res.json({ created: cache.set(req.params.key, payload) });
});
app.put('/api/:key', async (req, res) => {
const payload = req.body;
res.json({ updated: cache.update(req.params.key, payload) });
});
app.delete('/api/:key', async (req, res) => {
cache.remove(req.params.key);
res.json({ deleted: req.params.key });
});
```
---
<!-- _class: aufgabe -->
# Express – server.listen
# Selbstlernen — REST-API mit DB
```javascript
const server = app.listen(port, () => {
console.log(`Server läuft auf Port ${port}`);
});
Erweitert eure Kap-3-API:
server.on('error', (err) => {
console.error('Server error:', err);
});
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.
server.close(() => {
console.log('Server geschlossen');
});
```
---
# Zusammenfassung
# WebSockets
```javascript
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client verbunden');
ws.on('message', (data) => {
console.log('Empfangen:', data.toString());
ws.send('Pong');
});
});
```
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/ts-js-compilation.png)
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/docker-orchestration.png)
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/test-automation-trophy.png)
**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.