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

381 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
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); }
</style>
<!-- _class: lead -->
# Kapitel 4 — Node.js Advanced
## async · Files · Database · Auth · Errors
---
<!-- _class: lead -->
# Asynchron + Promises
---
# Synchron vs. Asynchron
**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.
---
# Promise — Anatomie
```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);
});
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 });
});
```
---
<!-- _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*
```javascript
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
```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' });
});
```
4 Argumente: `(err, req, res, next)` → Express erkennt Error-Handler.
---
<!-- _class: aufgabe -->
# 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.