kap1 web_eng (104 → 57 slides): kurs-intro + prüfungsleistung-übersicht, internet-timeline (4 milestones), URL→pixel-7-schritte, HTTP, onboarding (node 24 LTS via fnm, brew/winget paket-manager, VS Code/Chromium/Hoppscotch, git setup mit SSH-key, projekt-skeleton), HTML (anatomie, self-closing, grundgerüst, semantik-tabelle, native elemente details/dialog/input-types), A11y volle tiefe (BFSG+EAA 28.06.2025, WCAG-POUR, Perceivable/Operable/Understandable/Robust einzeln mit code, testen mit lighthouse/axe/manuell), CSS-vorschau (anatomie/selektoren/spezifizität/box-model/farben/einheiten/pseudos), JS-vorschau (ECMAScript-timeline/grundlagen/DOM/fetch), frameworks-übersicht (react/vue/svelte/astro counter-vergleich), build-tools/rendering-modi, selbstlernen erste-eigene-seite kap2 css_extended (13 → 28 slides): box-model + box-sizing global, flexbox basics+achsen+item-properties+typische-patterns, grid basics+areas+auto-layout, flexbox-vs-grid tabelle, mobile-first media queries, viewport-meta, fluid sizing mit clamp(), transitions (was performant), keyframe-animationen, prefers-reduced-motion pflicht, CSS-variablen + dark-mode-pattern, CSS functions (calc/clamp/gradient/oklch/filter), CSS-frameworks tabelle + tailwind pro/contra, selbstlernen flexboxfroggy+cssgridgarden kap3 nodejs_basics (~22 → 20 slides): was-ist-node + warum-node-tabelle, fnm versions-manager + node 24 LTS + .nvmrc, npm init + package.json (mit type:module), ESM modern + CJS legacy, built-in modules (node: prefix), HTTP-server eingebaut + express, REST mit express (GET/POST/PUT/DELETE-pattern), alternative frameworks tabelle (fastify/hono/koa), routing-pattern mit Router, fetch global ab node 18, error-handling res.ok, environment variables mit dotenv + niemals committen, selbstlernen mini-API kap4 nodejs_advanced (~24 → 22 slides): synchron-vs-asynchron, promise then-vs-async/await, sequenziell-vs-parallel + Promise.all, promise-methoden tabelle (all/allSettled/race/any), fs/promises lesen+schreiben, streams pipeline für große dateien, DB-übersicht SQLite/Postgres/MongoDB/Redis, better-sqlite3 prepared statements, ORM-übersicht (drizzle/prisma/knex), CRUD-pattern express+sqlite, auth-strategien tabelle, bcrypt für passwörter, JWT-pattern mit httpOnly+secure cookie, try/catch + error-klassen, globaler express-error-handler, selbstlernen REST+DB+auth kap5 testing (~23 → 23 slides): test-pyramide 70%/20%/10%, vitest setup + describe/it/expect, AAA-pattern, matchers tabelle, TDD red-green-refactor mit konkretem beispiel, supertest für API-integration, test-DB :memory: + lifecycle hooks, mocking mit vi.spyOn (sparsam), playwright E2E mit role-based-locators, E2E-vs-unit tradeoffs, coverage realistisch (80% gut), CI mit github-actions, selbstlernen tests-schreiben kap6 typescript (~30 → 19 slides): was-ist-TS + warum-tabelle, setup mit tsx + tsconfig.json + strict:true, basis-typen + any vermeiden, object-typen + interfaces + type-aliases, function-typen (Promise<T>), union + discriminated union + intersection, generics + constraints, built-in generics (Partial/Omit/Record), typ-inference (nicht über-annotieren), typen für REST-API mit express, zod runtime-validation + z.infer, any-vs-unknown-vs-never, best practices, selbstlernen API-zu-TS-migration kap7 docker (~24 → 24 slides): auf-meinem-rechner-läufts problem, container-vs-VM tabelle, docker basics (image/container/dockerfile/registry), dockerfile hallo-welt, schichten + cache-optimierung, multi-stage build, base-image-größen tabelle (alpine ~180MB vs node ~1.1GB), .dockerignore, compose.yml beispiel, compose-commands, networking (service-namen als hostnamen), volumes (named vs bind), env+secrets, github-actions docker-build, registry-übersicht, health-checks, best-practices, selbstlernen app-dockerizen kap8 best_practices (~30 → 28 slides): sinnvolle commits + conventional commits format, .gitignore template, branches+PRs workflow, README-template mit 6 sektionen, SOLID 5 prinzipien tabelle, S-single-responsibility + D-dependency-inversion mit code, 12-factor wichtigste (codebase/deps/config/processes/disposability), factor 3 config-in-env, factor 6 stateless-processes, SemVer MAJOR.MINOR.PATCH, package.json caret/tilde/exakt, code-reviews geben+nehmen + checkliste, doku-quellen tabelle (MDN/nodejs/npm/tc39/caniuse), stack-overflow gut-nutzen, AI-assistenten pro/contra (verstehen-nicht-kopieren), selbstlernen projekt-hygiene-check build geht durch alle 8 kapitel-html+pdf render-spot-check: cover-slide-dark-mode-invert, git-setup code-syntax-highlight, css-anatomie code-blocks — alle sauber kein deploy, nur build+verify
8.3 KiB
8.3 KiB
marp, theme, paginate, backgroundColor, header, footer
| marp | theme | paginate | backgroundColor | header | footer |
|---|---|---|---|---|---|
| true | gaia | true | Web Engineering – DHBW Stuttgart | Michael Czechowski – SoSe 2026 |
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:
- SQLite-DB mit
better-sqlite3 - CRUD mit DB statt In-Memory-Array
- Validierung (z.B. mit
zod) für POST-Body - Auth: einfache Login-Route mit bcrypt + JWT
- 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.