diff --git a/Makefile b/Makefile
index ba47421..175cd44 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ SLIDES_DIR = slides
# DHBW course settings
dhbw_NAME = Technik I – Grundlagen IT
-dhbw_KAPITEL = 01_web_eng 02_css_extended 03_nodejs_basics 04_nodejs_advanced
+dhbw_KAPITEL = 01_web_eng 02_css_extended 03_nodejs_basics 04_nodejs_advanced 05_testing 06_typescript 07_docker 08_best_practices
# Deploy paths
HDM_DEPLOY_PATH = /home/tengo/html/hdm
diff --git a/slides/dhbw/01_web_eng.md b/slides/dhbw/01_web_eng.md
index fc7a5db..50feaf0 100644
--- a/slides/dhbw/01_web_eng.md
+++ b/slides/dhbw/01_web_eng.md
@@ -4,7 +4,7 @@ theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
-footer: "Michael Czechowski – SoSe 2025"
+footer: "Michael Czechowski – SoSe 2026"
title: Web Engineering
---
@@ -98,11 +98,52 @@ a {
---
-# Prüfungsleistung
+# Prüfungsleistung – Übersicht
-**LN** – Lernnachweis (50%)
-- Projekte / Hausaufgaben
-- Präsentation
+Eigenes Projekt: **Web-App** ODER **Backend** (API/CLI).
+
+**Grundanforderungen (75 Punkte)**
+
+| Punkte | Bereich |
+|--------|---------|
+| 20 | Idee, Konzeption, Planung |
+| 5 | Plattformunabhängigkeit |
+| 25 | Clean Code (KISS, SOLID, DI, Testing, Error Handling) |
+| 15 | Präsentation |
+| 10 | Dokumentation |
+
+**Zusatzpunkte (max. 10):** TypeScript, Docker, Dev/Prod-Parity, `.env`, npm-Publish, Domain, HTTPS, Responsive Design.
+
+---
+
+# Prüfungsleistung – Idee & Konzeption (20 P.)
+
+- "Powerpoint"-Präsentation + Folien
+- Aussagekräftiger Arbeitstitel + Beschreibung
+- **Elevator Pitch** (max. 1 Min.)
+- Repo-Link (GitHub / GitLab / Codeberg / BitBucket)
+- **Logs**: wie hat sich Projekt verändert vs. Ursprungsidee?
+- Schematischer Projektaufbau (UML o. Ä.)
+
+---
+
+# Prüfungsleistung – Clean Code (25 P.)
+
+- `README.md` (clone, start, contribute)
+- **KISS** – Keep It Simple, Stupid
+- **SOLID** – SRP, OCP, LSP, ISP, DIP
+- **DI** – Dependency Injection
+- **Testing-Pyramide** (Unit, Integration, E2E)
+- **Exception/Error Handling**
+
+---
+
+# Prüfungsleistung – Abgabe & Termine
+
+- **Code-Upload:** bis 27.07.
+- **Präsentation:** 17.07. (Gruppen, ~10 Min.)
+
+Details: https://git.dailysh.it/DHBW/pruefungsleistung
---
@@ -411,6 +452,205 @@ async function loadProducts() {

+---
+
+# JavaScript Frameworks – Kategorien
+
+| Kategorie | Beispiele |
+|-----------|-----------|
+| **CSS Frameworks** | Tailwind, Bootstrap, shadcn/ui |
+| **Frontend Frameworks** | React, Vue, Svelte, Astro |
+| **Rendering / Meta** | Next.js, Nuxt, Gatsby |
+| **Build Tools / Bundler** | Webpack, Vite, Parcel, esbuild |
+| **Backend Frameworks** | Express, Fastify, NestJS |
+
+---
+
+# Vanilla JS – Counter
+
+```html
+
+
+```
+
+✓ hohe Kompatibilität, simpel
+✗ State-Management, Re-Rendering wird schnell komplex
+
+---
+
+# React – Counter (JSX)
+
+```jsx
+import { useState } from "react";
+
+function MyButton() {
+ const [count, setCount] = useState(0);
+
+ return (
+
+ );
+}
+```
+
+- **Component-Based** Architecture
+- **Virtual DOM**, JSX, Hooks
+- großes Ökosystem (React Router, Redux, …)
+
+---
+
+# Vue 3 – Composition API
+
+```vue
+
+
+
+
+
+```
+
+- **Reactive Data Binding**
+- Single-File Components (`.vue`)
+- Vue Router, Pinia (State)
+
+---
+
+# Svelte – Counter
+
+```svelte
+
+
+
+```
+
+- **Compiler-Based** – kein Runtime-Framework
+- Reactive Assignments (`let`)
+- Built-in Animationen
+
+---
+
+# Astro – Content-First
+
+```astro
+---
+const greeting = "Hallo DHBW";
+---
+
+
+ {greeting}
+
+
+
+```
+
+- **Islands Architecture** – Komponenten mehrerer Frameworks mischen
+- Default: kein JS im Output (nur statisches HTML)
+- Hydration on demand (`client:load`, `client:visible`)
+
+---
+
+# Rendering Frameworks
+
+| | Frontend | SSR | SSG | File Routing |
+|---|---|---|---|---|
+| **Next.js** | React | ✓ | ✓ | `/app/page.js` |
+| **Nuxt** | Vue | ✓ | ✓ | `/pages/*.vue` |
+| **Gatsby** | React | – | ✓ | GraphQL Data |
+
+---
+
+# Build Tools – Webpack
+
+- Module Bundling (JS, CSS, Images)
+- Module Loaders + großes Plugin-Ökosystem
+- Code Splitting, HMR
+- Konfigurations-lastig (`webpack.config.js`)
+
+→ Standard für viele Legacy-Setups.
+
+---
+
+# Build Tools – Vite
+
+```javascript
+// vite.config.js
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ server: {
+ proxy: {
+ "/api": {
+ target: "http://localhost:4567",
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, "")
+ }
+ }
+ }
+});
+```
+
+- Rollup unter der Haube, **HMR mit ES Modules**
+- Tree-Shaking, Code Splitting, React + Vue out-of-the-box
+
+---
+
+# Build Tools – Parcel
+
+```json
+{
+ "name": "my-project",
+ "source": "src/index.html",
+ "browserslist": "> 0.5%, last 2 versions, not dead",
+ "scripts": {
+ "start": "parcel",
+ "build": "parcel build"
+ },
+ "devDependencies": {
+ "parcel": "latest"
+ }
+}
+```
+
+- **Zero-Config**
+- Auto-Resolution, HMR
+- TS / CSS-Preprocessors out-of-the-box
+
+---
+
+# Microfrontends
+
+- Mehrere unabhängige Frontend-Apps in einer Page
+- Module Federation (Webpack 5)
+- **single-spa**: https://single-spa.js.org/
+- Anwendungsfall: große Teams, unterschiedliche Tech-Stacks pro Domain
+
+→ Komplexitätskosten: nur bei echtem Skalierungsbedarf.
+
+---
+
+# Demo-Repos
+
+- https://github.com/nextlevelshit/dhbw-client-js (Counter, multi-framework)
+- https://github.com/nextlevelshit/node-cache-api (API + Tests)
+- https://github.com/nextlevelshit/dhbw-docker (Compose-Stack)
+
---
# Fragen?
diff --git a/slides/dhbw/02_css_extended.md b/slides/dhbw/02_css_extended.md
index fa5f78d..0cd9185 100644
--- a/slides/dhbw/02_css_extended.md
+++ b/slides/dhbw/02_css_extended.md
@@ -4,7 +4,7 @@ theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
-footer: "Michael Czechowski – SoSe 2025"
+footer: "Michael Czechowski – SoSe 2026"
---
+
+
+
+
+
+# Testing
+
+## Unit · Integration · End-to-End
+
+---
+
+# Inhalt
+
+1. Test Automation Pyramid
+2. Test Automation Trophy
+3. Static Code Analysis
+4. Unit Tests
+5. Integration Tests
+6. End-to-End Tests (API + Client)
+7. Live Coding
+
+---
+
+# Test Automation Pyramid
+
+```
+ ▲ manual / exploratory
+ ▲ ▲ E2E (UI + API)
+ ▲▲▲▲ Integration (class + real deps)
+ ▲▲▲▲▲▲ Unit (class + mocked deps)
+ ▲▲▲▲▲▲▲▲ Static analysis (compiler, linter)
+```
+
+- Oben: wenige, langsam, teuer, brüchig
+- Unten: viele, schnell, billig, stabil
+
+---
+
+# Test Automation V-Modell
+
+```
+Requirements ────────────► Acceptance Test
+ ▼ ▲
+Architecture ────────► Integration Test
+ ▼ ▲
+Modular Design ──────► Unit Test
+ ▼ ▲
+ Implementation
+```
+
+Jede Design-Phase hat Testpendant. Testfälle früh ableiten.
+
+---
+
+# Test Automation Trophy (Kent C. Dodds)
+
+```
+ 🏆
+ ┌─────────┐
+ │ E2E │ ← klein
+ ├─────────┤
+ │ Integr. │ ← GROSS (Hauptfokus)
+ ├─────────┤
+ │ Unit │ ← mittel
+ ├─────────┤
+ │ Static │ ← Basis (TS, ESLint)
+ └─────────┘
+```
+
+Andere Gewichtung: **Integration im Zentrum**, Static als breite Basis.
+
+---
+
+# Testing Frameworks Übersicht
+
+**Static Code Analysis**
+- ESLint, Prettier, SonarQube, TypeScript
+
+**Unit + Integration**
+- jest, mocha, **vitest**
+
+**End-to-End**
+- **Cypress**, **Playwright**, supertest (API only)
+
+**Visual / Interaction**
+- Storybook, Chromatic
+
+---
+
+# Static Code Analysis
+
+- Läuft in Millisekunden
+- Findet Syntaxfehler, ungenutzte Variablen, undefined refs
+- Kein Runtime-Overhead
+- **Erste Verteidigungslinie**
+
+```bash
+npm i -D eslint prettier
+npx eslint --init
+npx prettier --write .
+```
+
+`SonarQube`: zentraler Server, Code Smells, Security, Coverage.
+
+---
+
+# Unit Tests – Eigenschaften
+
+- **Isolation** – jede Dependency gemockt
+- **Deterministisch** – gleicher Input → gleicher Output
+- **Schnell** – kein I/O, kein Netzwerk, kein FS
+- **Fokussiert** – eine Funktion pro Test
+
+---
+
+# Unit Test – Beispiel (vitest)
+
+```javascript
+// cache.unit.test.js
+import { describe, it, expect } from "vitest";
+import { Cache } from "./cache.js";
+
+describe("Cache", () => {
+ it("creates and retrieves data with custom key", () => {
+ const cache = new Cache();
+ const data = { user: "wolfgang", city: "stuttgart" };
+
+ const key = cache.create(data, "custom-key");
+
+ expect(key).toBe("custom-key");
+ expect(cache.get("custom-key")).toEqual(data);
+ });
+});
+```
+
+---
+
+# Integration Tests – Eigenschaften
+
+- **Reale Komponenten** – echte Cache + Express-Instanzen
+- **Supertest-Magie** – simuliert HTTP ohne Server-Startup
+- **In-Process** – alles im selben Node-Prozess
+- **Kein Netzwerk** – keine TCP-Connections
+
+→ teste Zusammenspiel mehrerer Module ohne Deployment.
+
+---
+
+# Integration Test – Beispiel (supertest)
+
+```javascript
+// app.integration.test.js
+import request from "supertest";
+import { app, cache } from "./app.js";
+
+test("full CRUD lifecycle with real cache", async () => {
+ const res = await request(app)
+ .post("/api/entries")
+ .send({ name: "integration-test", status: "active" });
+
+ expect(res.status).toBe(201);
+ const { key } = res.body;
+
+ expect(cache.keys()).toContain(key);
+});
+```
+
+---
+
+# End-to-End Tests (API)
+
+- **Echter Server** – HTTP auf TCP-Port
+- **Echtes Netzwerk** – `fetch` über localhost
+- **Echte Umgebung** – CORS, Middleware-Stack
+- **User-Perspektive** – exakt was Clients sehen
+
+---
+
+# E2E API Test – Beispiel
+
+```javascript
+// index.e2e.test.js
+import { createServer } from "node:http";
+import { app } from "./app.js";
+
+let server;
+const TEST_PORT = 8081;
+const baseUrl = `http://localhost:${TEST_PORT}`;
+
+beforeAll(async () => {
+ server = createServer(app);
+ await new Promise((r) => server.listen(TEST_PORT, r));
+});
+
+afterAll(() => server.close());
+
+test("complete user journey", async () => {
+ const res = await fetch(`${baseUrl}/api`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: "e2e", timestamp: Date.now() }),
+ });
+ expect(res.status).toBe(201);
+});
+```
+
+---
+
+# End-to-End Tests (Client)
+
+- **Echter Browser** – Chromium / Firefox / WebKit
+- **Echtes DOM** – Klicks, Tippen, Scrollen
+- **Echtes Rendering** – CSS, async Operationen
+- **Cross-Origin** – Client ↔ Server-Kommunikation
+
+Tools: **Cypress**, **Playwright**
+
+---
+
+# Cypress – Minimalbeispiel
+
+```javascript
+// cypress/e2e/counter.cy.js
+describe("Counter App", () => {
+ it("increments on click", () => {
+ cy.visit("http://localhost:3000");
+ cy.contains("button", "Clicked 0 times").click();
+ cy.contains("button", "Clicked 1 times");
+ });
+});
+```
+
+```bash
+npx cypress open # GUI
+npx cypress run # headless / CI
+```
+
+---
+
+# Playwright – Minimalbeispiel
+
+```javascript
+// tests/counter.spec.js
+import { test, expect } from "@playwright/test";
+
+test("counter increments", async ({ page }) => {
+ await page.goto("http://localhost:3000");
+ await page.getByRole("button").click();
+ await expect(page.getByRole("button")).toContainText("Clicked 1 times");
+});
+```
+
+```bash
+npx playwright test
+npx playwright test --ui
+```
+
+---
+
+# Coverage Reports
+
+```bash
+npx vitest run --coverage
+```
+
+```
+File | % Stmts | % Branch | % Funcs | % Lines
+--------------|---------|----------|---------|--------
+cache.js | 95.2 | 80.0 | 100.0 | 95.2
+app.js | 88.7 | 75.0 | 90.0 | 88.7
+```
+
+- `lcov.info` → CI / SonarQube
+- HTML Report → `coverage/index.html`
+
+---
+
+# Best Practices
+
+- **AAA**: Arrange · Act · Assert
+- Tests sind **Dokumentation** – beschreibend benennen
+- Ein Verhalten pro Test
+- Keine Logik im Test (keine `if`/`for`)
+- **Test Doubles**: stub, mock, spy, fake
+- CI: Tests bei jedem Push
+
+---
+
+# Live Coding
+
+Repo: `node-cache-api`
+
+```bash
+git clone https://github.com/nextlevelshit/node-cache-api
+cd node-cache-api
+npm install
+npm test # unit + integration
+npm run test:e2e # e2e
+```
+
+---
+
+
+
+
+# Fragen?
+
+**Hausaufgabe:** Eigene Projektidee mit mind. 1 Unit + 1 Integration Test starten.
diff --git a/slides/dhbw/06_typescript.md b/slides/dhbw/06_typescript.md
new file mode 100644
index 0000000..a19d88a
--- /dev/null
+++ b/slides/dhbw/06_typescript.md
@@ -0,0 +1,422 @@
+---
+marp: true
+theme: gaia
+paginate: true
+backgroundColor: #fff
+header: "Web Engineering – DHBW Stuttgart"
+footer: "Michael Czechowski – SoSe 2026"
+title: TypeScript
+---
+
+
+
+
+
+
+
+# TypeScript
+
+## JavaScript on Steroids
+
+---
+
+# Inhalt
+
+1. TypeScript: was und warum
+2. `tsc` + `ts-node`
+3. `tsconfig.json`
+4. Type Primitives
+5. Union Types, Type Alias, Interface
+6. Enum, Generics, Type Casting
+7. Compilation: types stripped
+8. Decorators + reflect-metadata
+
+---
+
+# TypeScript – Was?
+
+- **Superset von JavaScript** (Microsoft, 2012)
+- Jede `.js` lässt sich zu `.ts` umbenennen
+- **Type Errors zur Compile-/Transpile-Zeit**
+- Neuere ECMAScript-Features verfügbar
+- **Im Browser:** läuft nicht direkt – wird zu JS kompiliert
+
+---
+
+# Setup: tsc + ts-node
+
+```bash
+npm i -D typescript ts-node @types/node
+npx tsc --init # erzeugt tsconfig.json
+```
+
+**Run:**
+```bash
+npx ts-node index.ts # statt: node index.js
+```
+
+**Build:**
+```bash
+npx tsc # kompiliert nach ./dist
+node dist/index.js
+```
+
+---
+
+# tsconfig.json
+
+```json
+{
+ "compilerOptions": {
+ "target": "es2022",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
+```
+
+---
+
+# Type Primitives
+
+```typescript
+let loading: boolean = false;
+let count: number = 42;
+let pi: number = 3.14;
+let name: string = "DHBW ist fetzig!";
+let empty: null = null;
+let missing: undefined = undefined;
+
+function log(msg: string): void {
+ console.log(msg);
+}
+```
+
+`any`, `unknown`, `never`, `void` sind weitere TS-Spezialtypen.
+
+---
+
+# Union Types
+
+```typescript
+let id: number | string;
+
+id = 101; // ok
+id = "101"; // ok
+id = true; // ✗ TypeError
+```
+
+---
+
+# Type Alias
+
+```typescript
+type NumOrString = number | string;
+
+let id: NumOrString;
+id = 101;
+id = "abc";
+
+type UserId = string;
+type Status = "active" | "inactive" | "pending";
+```
+
+---
+
+# Interface
+
+```typescript
+interface User {
+ id: number;
+ name: string;
+ email?: string; // optional
+ readonly createdAt: Date; // schreibgeschützt
+ greet(msg: string): void;
+}
+
+const u: User = {
+ id: 1,
+ name: "Lisa",
+ createdAt: new Date(),
+ greet(msg) { console.log(msg); }
+};
+```
+
+---
+
+# Interface vs Type
+
+**Interface**
+- nur Objektformen
+- kann mehrfach deklariert (Merging)
+- bei perf-kritischen Checks oft schneller
+
+**Type Alias**
+- alles: Primitive, Union, Tuple, Function-Signaturen
+- nicht erweiterbar nach Definition
+
+→ Faustregel: **Interfaces für API-Shapes, Types für alles andere.**
+
+---
+
+# Enum
+
+```typescript
+enum Direction {
+ Up = 1,
+ Down, // 2
+ Left, // 3
+ Right // 4
+}
+
+function walk(d: Direction): string {
+ switch (d) {
+ case Direction.Up: return "bow";
+ case Direction.Down: return "stern";
+ case Direction.Left: return "windward";
+ case Direction.Right: return "leeward";
+ }
+}
+
+walk(Direction.Up); // "bow"
+```
+
+---
+
+# Generics
+
+```typescript
+class GenericContainer {
+ private readonly value: T;
+
+ constructor(value: T) {
+ this.value = value;
+ }
+
+ getValue(): T {
+ return this.value;
+ }
+}
+
+const numbers = new GenericContainer(10);
+const strings = new GenericContainer("Hello");
+
+console.log(numbers.getValue()); // 10
+console.log(strings.getValue()); // "Hello"
+```
+
+---
+
+# Type Casting
+
+```typescript
+let someValue: any = "this is a string";
+
+let len: number = (someValue as string).length;
+console.log(len); // 16
+
+// Alt-Syntax (nicht in TSX):
+let len2: number = (someValue).length;
+```
+
+`as unknown as T` für "double cast" – nur Notbehelf!
+
+---
+
+# Compilation: Types werden gestripped
+
+**TypeScript:**
+```typescript
+type Result = "pass" | "fail";
+
+function verify(result: Result) {
+ if (result === "pass") console.log("Passed");
+ else console.log("Failed");
+}
+```
+
+**JavaScript (nach `tsc`):**
+```javascript
+function verify(result) {
+ if (result === "pass") console.log("Passed");
+ else console.log("Failed");
+}
+```
+
+**Types existieren nur zur Compile-Zeit.** Kein Runtime-Check.
+
+---
+
+
+
+
+# Decorators
+
+## Meta-Programmierung mit TypeScript
+
+---
+
+# TypeScript: Decorators
+
+Vier Typen:
+
+- **Class Decorators** – auf ganze Klasse
+- **Property Decorators** – auf Felder
+- **Method Decorators** – auf Methoden
+- **Parameter Decorators** – auf Methoden-Parameter
+
+Genutzt von: Angular, NestJS, TypeORM, class-validator.
+
+---
+
+# Decorators aktivieren
+
+**`tsconfig.json`:**
+```json
+{
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
+ }
+}
+```
+
+**Root-File:**
+```typescript
+import "reflect-metadata";
+```
+
+---
+
+# Decorator-Signaturen
+
+```typescript
+type ClassDecorator = (target: T) => T | void;
+
+type PropertyDecorator = (
+ target: Object,
+ propertyKey: string | symbol
+) => void;
+
+type MethodDecorator = (
+ target: Object,
+ propertyKey: string | symbol,
+ descriptor: TypedPropertyDescriptor
+) => TypedPropertyDescriptor | void;
+
+type ParameterDecorator = (
+ target: Object,
+ propertyKey: string | symbol | undefined,
+ parameterIndex: number
+) => void;
+```
+
+---
+
+# Decorator – Beispiel
+
+```typescript
+function Log(target: any, key: string, desc: PropertyDescriptor) {
+ const original = desc.value;
+ desc.value = function (...args: any[]) {
+ console.log(`→ ${key}(${JSON.stringify(args)})`);
+ return original.apply(this, args);
+ };
+}
+
+class Calculator {
+ @Log
+ add(a: number, b: number) {
+ return a + b;
+ }
+}
+
+new Calculator().add(2, 3); // logs: → add([2,3])
+```
+
+---
+
+# Reflect: Meta-Daten lesen
+
+```typescript
+import "reflect-metadata";
+
+Reflect.defineMetadata("role", "admin", target);
+Reflect.hasMetadata("role", target); // true
+Reflect.getMetadata("role", target); // "admin"
+```
+
+Frameworks (NestJS, TypeORM) nutzen das, um zur **Runtime** Type-Informationen zu rekonstruieren – z. B. für DI, ORM-Mapping, Validierung.
+
+---
+
+# Anwendung: NestJS-Beispiel
+
+```typescript
+@Controller("users")
+export class UsersController {
+ constructor(private users: UsersService) {}
+
+ @Get(":id")
+ findOne(@Param("id") id: string) {
+ return this.users.find(+id);
+ }
+}
+```
+
+`@Controller`, `@Get`, `@Param` sind Decorators – Metadaten ⇒ Routing.
+
+---
+
+
+
+
+# Fragen?
+
+**Hausaufgabe:** Bestehendes JS-Projekt auf TS migrieren – mind. 1 File + `tsconfig.json`.
diff --git a/slides/dhbw/07_docker.md b/slides/dhbw/07_docker.md
new file mode 100644
index 0000000..4008799
--- /dev/null
+++ b/slides/dhbw/07_docker.md
@@ -0,0 +1,338 @@
+---
+marp: true
+theme: gaia
+paginate: true
+backgroundColor: #fff
+header: "Web Engineering – DHBW Stuttgart"
+footer: "Michael Czechowski – SoSe 2026"
+title: Docker und Service Orchestration
+---
+
+
+
+
+
+
+
+# Docker
+
+## Service Orchestration
+
+---
+
+# Inhalt
+
+1. Architektur: Reverse Proxy + API + DB
+2. Container vs VM
+3. `Dockerfile`
+4. `compose.yml`
+5. Docker CLI / Compose Befehle
+6. Live Demo: `dhbw-docker`
+
+---
+
+# Architektur
+
+```
+ Browser (localhost:10007)
+ │
+ ▼
+ ┌──────────────────┐
+ │ Reverse Proxy │ (nginx / traefik)
+ └─────────┬────────┘
+ ┌────┴────┐
+ ▼ ▼
+ ┌─────────┐ ┌────────┐
+ │ Web │ │ API │
+ │ App │ │ (Node) │
+ └────┬────┘ └────┬───┘
+ └─────┬──────┘
+ ▼
+ ┌────────┐
+ │ DB │ (Postgres)
+ └────────┘
+```
+
+---
+
+# Container vs VM
+
+| | VM | Container |
+|---|----|-----------|
+| Kernel | eigener | Host-Kernel |
+| Boot | Minuten | Sekunden |
+| Größe | GB | MB |
+| Isolation | stark | namespaces, cgroups |
+| Image | komplett | Layer (diff) |
+
+→ **Docker** = Container-Engine + Image-Format + Registry.
+
+---
+
+# Dockerfile – Express API
+
+```dockerfile
+FROM node:lts-slim AS builder
+
+WORKDIR /app
+
+COPY ./package*.json ./
+RUN npm ci --no-audit --no-fund --no-progress --loglevel=error
+
+COPY ./ ./
+
+ENV NODE_ENV="production"
+
+EXPOSE 8080
+
+CMD ["npm", "start"]
+```
+
+**`npm ci`** statt `npm i` für reproduzierbare Builds.
+
+---
+
+# Multi-Stage Build
+
+```dockerfile
+FROM node:lts-slim AS builder
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+FROM node:lts-slim AS runtime
+WORKDIR /app
+COPY --from=builder /app/dist ./dist
+COPY --from=builder /app/node_modules ./node_modules
+COPY package*.json ./
+EXPOSE 8080
+CMD ["node", "dist/index.js"]
+```
+
+→ kleines Final-Image, keine Build-Tools im Runtime.
+
+---
+
+# .dockerignore
+
+```
+node_modules
+dist
+.git
+.env
+*.log
+coverage
+```
+
+Spart Build-Kontext und verhindert Geheimnis-Leaks.
+
+---
+
+# compose.yml – Services
+
+```yaml
+name: dhbw-docker-app
+
+services:
+ api:
+ build: ./api
+ container_name: dhbw-api
+ environment:
+ DATABASE_USERNAME: ${DATABASE_USERNAME}
+ DATABASE_PASSWORD: ${DATABASE_PASSWORD}
+ DATABASE_NAME: ${DATABASE_NAME}
+ DATABASE_HOST: db
+ DATABASE_PORT: 5432
+ ports:
+ - "10000:8080"
+ networks: [net, data]
+ depends_on:
+ db:
+ condition: service_healthy
+```
+
+---
+
+# compose.yml – Datenbank + Proxy
+
+```yaml
+ db:
+ image: postgres:16-alpine
+ environment:
+ POSTGRES_USER: ${DATABASE_USERNAME}
+ POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
+ POSTGRES_DB: ${DATABASE_NAME}
+ volumes:
+ - dbdata:/var/lib/postgresql/data
+ networks: [data]
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $$DATABASE_USERNAME"]
+ interval: 5s
+ retries: 5
+
+ proxy:
+ image: nginx:alpine
+ ports: ["10007:80"]
+ volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
+ networks: [net]
+ depends_on: [api]
+
+volumes:
+ dbdata:
+
+networks:
+ net:
+ data:
+```
+
+---
+
+# Docker Compose – Befehle
+
+```bash
+docker compose -p "dhbw-docker-app" \
+ -f compose.yml \
+ --env-file .env \
+ up \
+ --build \
+ --remove-orphans \
+ --force-recreate
+```
+
+| Flag | Bedeutung |
+|------|-----------|
+| `-p` | Projektname |
+| `-f` | mehrere Compose-Files möglich |
+| `--env-file` | `.env` einlesen |
+| `--build` | Images neu bauen |
+| `--remove-orphans` | verwaiste Container weg |
+| `--force-recreate` | Container neu starten |
+
+---
+
+# Compose – Alltag
+
+```bash
+docker compose up -d # detached starten
+docker compose ps # Status
+docker compose logs -f api # Logs streamen
+docker compose exec api sh # Shell im Container
+docker compose down -v # Stop + Volumes löschen
+docker compose restart api # Neustart einzelner Service
+```
+
+---
+
+# Health Checks
+
+```yaml
+api:
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
+ interval: 10s
+ timeout: 3s
+ retries: 3
+ start_period: 5s
+```
+
+`depends_on: { condition: service_healthy }` wartet, bis Health passt.
+
+---
+
+# Networks: Segmentierung
+
+- **`net`** – öffentlich erreichbar (Proxy ↔ App)
+- **`data`** – nur intern (App ↔ DB)
+- DB ist **nicht** vom Internet erreichbar
+- Service-DNS: `api`, `db`, `proxy` (Container-Name = Hostname)
+
+→ Defense in Depth.
+
+---
+
+# Volumes – Daten persistent
+
+```yaml
+volumes:
+ dbdata: # benannt
+ - ./code:/app # bind mount (dev)
+ - /app/node_modules # anonymous (überschreibt bind)
+```
+
+`docker compose down` ohne `-v` lässt Volumes leben.
+
+---
+
+# Live Demo
+
+Repo: https://github.com/nextlevelshit/dhbw-docker
+
+```bash
+git clone https://github.com/nextlevelshit/dhbw-docker
+cd dhbw-docker
+cp .env.example .env
+docker compose up --build
+```
+
+→ http://localhost:10007
+
+---
+
+# 100 Concepts of Docker (Exkurs)
+
+- Image · Container · Volume · Network
+- Dockerfile · Layer Cache · BuildKit
+- Compose · Stack · Swarm · Kubernetes
+- Registry · Tag · Digest
+- Dev/Prod-Parity (12-Factor)
+- Secrets · Configs
+- Logging Drivers
+- Health Checks · Restart Policies
+
+---
+
+
+
+
+# Fragen?
+
+**Hausaufgabe:** Eigenes Projekt mit `Dockerfile` + `compose.yml` containerisieren.
diff --git a/slides/dhbw/08_best_practices.md b/slides/dhbw/08_best_practices.md
new file mode 100644
index 0000000..f412871
--- /dev/null
+++ b/slides/dhbw/08_best_practices.md
@@ -0,0 +1,346 @@
+---
+marp: true
+theme: gaia
+paginate: true
+backgroundColor: #fff
+header: "Web Engineering – DHBW Stuttgart"
+footer: "Michael Czechowski – SoSe 2026"
+title: Best Practices
+---
+
+
+
+
+
+
+
+# Best Practices
+
+## Wrap-up & Projektsupport
+
+---
+
+# Inhalt
+
+1. Semantic Versioning
+2. Git Commit Messages (Udacity Style)
+3. 12 Factor App
+4. Make it Work · Make it Right · Make it Fast
+5. Präsentations-Tipps
+6. Feedback (Sandwich-Methode)
+7. Prüfungsleistung Recap
+
+---
+
+# Semantic Versioning (SemVer 2.0.0)
+
+```
+MAJOR.MINOR.PATCH
+ │ │ └─ Bugfix, abwärtskompatibel
+ │ └────── Neue Funktion, abwärtskompatibel
+ └──────────── Breaking Change
+```
+
+**Beispiele:**
+- `1.4.2` → `1.4.3` Bugfix
+- `1.4.3` → `1.5.0` neues Feature
+- `1.5.0` → `2.0.0` Breaking Change
+
+Pre-Release: `1.0.0-alpha.1`, `1.0.0-rc.2`
+
+→ https://semver.org/
+
+---
+
+# package.json: Versionsranges
+
+```json
+{
+ "dependencies": {
+ "express": "^4.19.2", // ≥4.19.2 < 5.0.0 (gleiche MAJOR)
+ "lodash": "~4.17.21", // ≥4.17.21 < 4.18.0 (gleiche MINOR)
+ "react": "19.0.0" // exakt
+ }
+}
+```
+
+`package-lock.json` pinnt die **tatsächlich installierten** Versionen.
+
+---
+
+# Git Commit Messages – Udacity Style
+
+```
+:
+
+
+
+