dhbw phase 4: alle 8 kapitel slide-MD komplett (4176 zeilen → ~1100 zeilen, ~210 slides total)

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
This commit is contained in:
2026-05-14 17:14:02 +02:00
parent 443150c40e
commit 4053185e74
8 changed files with 2623 additions and 3163 deletions
+479 -1439
View File
File diff suppressed because it is too large Load Diff
+362 -201
View File
@@ -8,217 +8,373 @@ footer: "Michael Czechowski – SoSe 2026"
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; section.invert h2 { color: #f48fb1; }
} pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
section { pre code { background: transparent; color: inherit; }
font-size: 1.4rem; code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
} a { color: var(--color-highlight); }
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 2 — CSS Extended
## Box-Model · Flexbox · Grid · Responsive · Animations
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 2 — HTML & CSS (Frameworks)
--- ---
# Ressourcen zum Selbstlernen # Recap Kap 1
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/) | Bereich | Was wir kennen |
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/) |---------|----------------|
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/) | Anatomie | `selector { property: value }` |
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/) | Selektoren | element, `.class`, `#id`, `:hover` |
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/) | Spezifizität | inline > id > class > element |
| Farben | hex, rgb, hsl, oklch |
| Einheiten | px, rem, em, %, vw, vh |
**Heute:** Layout + Animation in Tiefe.
--- ---
# Inhalt # Box-Model — die Erinnerung
Aufbauend auf den Grundlagen aus Sitzung 1. ```
┌──────────────────┐
│ margin │
│ ┌────────────┐ │
│ │ border │ │
│ │ ┌────────┐ │ │
│ │ │padding │ │ │
│ │ │content │ │ │
│ │ └────────┘ │ │
│ └────────────┘ │
└──────────────────┘
```
1. Attribut-Selektoren `box-sizing: border-box` global setzen.
2. Combinators (vertieft)
3. Pseudo-Klassen — erweitert
4. Shorthand Properties
5. Flexbox-Shorthands
6. Custom Properties (Variables)
7. CSS Functions (`calc`, `clamp`, ...)
8. Transition & Animation
9. CSS Frameworks (Tailwind & Co.)
--- ---
<!-- _class: invert --> # Box-Model in Code
<!-- _backgroundColor: #000 -->
# CSS: Extended
---
# CSS Selectors – Attribut-Selektoren
```css ```css
/* presence */ * { box-sizing: border-box; }
[disabled] { opacity: 0.5; }
/* exact value */ .card {
[type="email"] { border-color: blue; } width: 320px; /* inkl. padding + border */
padding: 1.5rem;
border: 2px solid #ccc;
border-radius: 0.75rem;
margin: 1rem auto; /* Außen + horizontal zentriert */
}
```
/* contains word */ Inspector im DevTools zeigt das Box-Model live an.
[class~="btn"] { cursor: pointer; }
/* starts with */ ---
[href^="https"] { color: green; }
/* ends with */ <!-- _class: lead -->
[src$=".png"] { border: 1px solid #ccc; }
/* contains substring */ # Flexbox
[class*="icon"] { padding-left: 20px; } ## 1D-Layout (Reihe ODER Spalte)
---
# Flexbox — Basics
```css
.container {
display: flex;
gap: 1rem; /* Abstand zwischen Items */
flex-direction: row; /* row | column */
justify-content: space-between; /* Haupt-Achse */
align-items: center; /* Quer-Achse */
flex-wrap: wrap; /* umbruchen erlaubt */
}
```
```html
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
``` ```
--- ---
# CSS Combinators # Flexbox — Achsen
**`flex-direction: row` (default):** Haupt-Achse horizontal.
```
┌─────────────────────────────────────┐
│ ◄── justify-content ──► │
│ ▲ │
│ │ │
│ align-items │
│ │ │
│ ▼ │
└─────────────────────────────────────┘
```
`column`: Achsen drehen sich 90°.
---
# Flexbox — Item-Properties
```css ```css
/* Child (direkt) */ .item {
div > p { margin: 0; } flex: 1; /* nimm gleiche Anteile vom verfügbaren Raum */
flex: 0 0 200px; /* fest 200px, kein wachsen, kein schrumpfen */
flex-grow: 2; /* doppelte Anteile */
order: -1; /* visuell zuerst, im DOM hinten */
align-self: end; /* override align-items für DIESES Item */
}
```
/* Descendant (Nachkomme) */ **Shorthand:** `flex: <grow> <shrink> <basis>`.
div p { margin: 0; }
/* Next Sibling */ ---
h1 + p { font-size: 1.2em; }
/* Subsequent Sibling */ # Typische Flex-Patterns
h1 ~ p { color: #666; }
/* Selector List */ ```css
h1, h2, h3 { font-weight: bold; } /* Navigation: Logo links, Items rechts */
.nav { display: flex; justify-content: space-between; }
/* Karte: Bild + Text nebeneinander */
.card { display: flex; gap: 1rem; align-items: center; }
/* Button-Gruppe: alle gleich groß */
.btn-group { display: flex; gap: 0.5rem; }
.btn-group button { flex: 1; }
/* Footer: gleichmäßig verteilt */
.footer { display: flex; justify-content: space-around; }
``` ```
--- ---
# Pseudo Classes – Erweitert <!-- _class: lead -->
```css # Grid
/* Form States */ ## 2D-Layout (Reihe UND Spalte)
:valid { border-color: green; }
:invalid { border-color: red; }
:placeholder-shown { color: #999; }
/* Content */
:before { content: "→ "; }
:after { content: " [Wichtig]"; }
:first-letter { font-size: 2em; }
:first-line { font-weight: bold; }
/* NOT */
:not(.disabled) { pointer-events: auto; }
:not(:last-child) { border-bottom: 1px solid #ccc; }
```
--- ---
# Shorthand Properties # Grid — Basics
```css ```css
/* margin: top right bottom left */ .container {
margin: 10px 20px 10px 20px; display: grid;
grid-template-columns: 1fr 2fr 1fr; /* 3 Spalten, Verhältnis 1:2:1 */
/* centering */ grid-template-rows: auto 1fr auto; /* Header, Body, Footer */
margin: 0 auto; gap: 1rem;
}
/* padding same */
padding: 20px;
/* border: width style color */
border: 1px solid #333;
/* background: color image position/size repeat */
background: #fff url(logo.png) 0 0 no-repeat;
/* font: style variant weight size/line-height family */
font: italic normal bold 16px/1.5 Arial, sans-serif;
``` ```
`fr` = Fraction Unit — verbleibender Raum aufgeteilt.
--- ---
# Flexbox Shorthands # Grid — Areas (named layout)
```css ```css
/* flex: grow shrink basis */ .layout {
flex: 1 0 auto; display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 200px 1fr;
}
/* place-items: align-items justify-items */ .layout header { grid-area: header; }
place-items: center stretch; .layout nav { grid-area: nav; }
.layout main { grid-area: main; }
/* place-content: align-content justify-content */ .layout footer { grid-area: footer; }
place-content: space-between center;
/* gap: row-gap column-gap */
gap: 20px 40px;
``` ```
Lesbarer als Spalten-/Zeilen-Nummern.
--- ---
# CSS Custom Properties (Variables) # Grid — Auto-Layout
```css
/* So viele Spalten wie reinpassen, jede min. 200px */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
```
Mobile: 1 Spalte. Tablet: 2-3. Desktop: 4-5. **Ohne Media Queries.**
---
# Flexbox vs. Grid
| | Flexbox | Grid |
|--|---------|------|
| Dimension | 1D | 2D |
| Use-Case | Navigation, Karten-Reihe, Button-Gruppe | Seiten-Layout, komplexes Raster |
| Item-Größe | flexibel | meist fest |
| Direction | row/column | rows + columns |
**Beide in einem Layout möglich:** Grid für Seite, Flexbox in Komponenten.
---
<!-- _class: lead -->
# Responsive Design
---
# Mobile-First mit Media Queries
```css
/* Default: Mobile */
.sidebar { display: none; }
.content { padding: 1rem; }
/* Tablet aufwärts */
@media (min-width: 768px) {
.sidebar { display: block; width: 240px; }
.content { padding: 2rem; }
}
/* Desktop */
@media (min-width: 1280px) {
.sidebar { width: 320px; }
}
```
**Mobile-First**: kleinste Größe als Default, dann größer.
---
# Viewport-Meta
```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```
**Pflicht** im `<head>`. Ohne dieses Meta zoomt iPhone & Android alles auf 980 px Breite.
---
# Fluid Sizing — ohne Media Queries
```css
/* Font: skaliert zwischen 14px und 22px */
h1 { font-size: clamp(1.4rem, 2.5vw + 1rem, 2.5rem); }
/* Padding: skaliert mit Viewport */
.card { padding: clamp(1rem, 3vw, 3rem); }
/* Breite: nie mehr als 70 Zeichen für Lesetext */
.prose { max-width: 70ch; margin: auto; }
```
`clamp(min, ideal, max)` ist modernes Allzweck-Werkzeug.
---
<!-- _class: lead -->
# Animationen
---
# Transitions — sanfter Wechsel
```css
button {
background: hotpink;
transition: background 0.2s ease, transform 0.15s;
}
button:hover {
background: deeppink;
transform: translateY(-2px);
}
```
**Was animiert performant:** `transform`, `opacity`. **Was nicht:** `width`, `height`, `top/left`.
---
# Keyframe-Animationen
```css
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.notification {
animation: pulse 1.5s ease infinite;
}
```
`animation: <name> <duration> <timing> <iteration>`.
---
# Reduced Motion — Pflicht
Nicht alle wollen Animationen. (Vestibuläre Probleme, ADHS, Migräne.)
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
**Standard-Bestandteil** jeder Seite — ein User-Setting respektieren.
---
<!-- _class: lead -->
# CSS-Variablen + Modern
---
# Custom Properties (CSS Variables)
```css ```css
:root { :root {
--primary-color: #d63384; --primary: hotpink;
--spacing: 20px; --gap: 1rem;
--radius: 8px; --radius: 0.75rem;
} }
.button { button {
background: var(--primary-color); background: var(--primary);
padding: var(--spacing); padding: var(--gap);
border-radius: var(--radius); border-radius: var(--radius);
} }
/* Dark Mode: nur Variablen umsetzen */
@media (prefers-color-scheme: dark) {
:root { --primary: deeppink; }
}
``` ```
--- ---
@@ -226,67 +382,72 @@ gap: 20px 40px;
# CSS Functions # CSS Functions
```css ```css
/* calc */ .box {
width: calc(100% - 40px); width: calc(100% - 2rem); /* Mathe */
font-size: clamp(1rem, 2vw, 2rem); /* min, ideal, max */
/* min/max/clamp */ background: linear-gradient(45deg, hotpink, deeppink);
width: min(100%, 600px); color: oklch(0.7 0.2 30); /* moderner Farbraum */
font-size: clamp(14px, 2vw, 20px); filter: blur(2px) brightness(1.2); /* Bildeffekte */
/* var with fallback */
color: var(--text-color, #333);
/* rgb/rgba/hsl/hsla */
color: hsl(200, 100%, 50%);
background: rgba(0, 0, 0, 0.5);
```
---
# Transition & Animation
```css
/* transition: property duration timing-function */
transition: all 0.3s ease-in-out;
transition: background 0.2s, transform 0.1s;
/* animation */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
} }
``` ```
--- ---
# CSS Frameworks # CSS-Frameworks
| Framework | Stil | | Framework | Stil |
|-----------|------| |-----------|------|
| **Bootstrap** | Komponenten + Grid, opinionated | | **Tailwind CSS** | Utility-Klassen direkt im HTML |
| **Tailwind CSS** | Utility-First, atomar | | **Bootstrap** | Komponenten-Bibliothek (klassisch) |
| **Bulma** | klassisch, ohne JS | | **Pico** | klassen-frei, semantisches HTML reicht |
| **shadcn/ui** | kopierbare Komponenten (Tailwind-basiert) | | **Open Props** | CSS-Variablen-Set |
| **Flowbite** | Tailwind-Komponentenbib. |
---
# Tailwind CSS – Beispiel
```html ```html
<button class="bg-blue-600 hover:bg-blue-700 <!-- Tailwind: alle Styles als Klassen -->
text-white font-semibold <button class="bg-pink-500 hover:bg-pink-600 px-4 py-2 rounded-md">
px-4 py-2 rounded-lg Klick
transition shadow-sm">
Klick mich
</button> </button>
``` ```
- Keine eigenen CSS-Klassen mehr ---
- JIT-Compiler erzeugt nur genutzte Klassen
- Design-System via `tailwind.config.js`
# Tailwind — Argumente dafür/dagegen
**Pro:**
- Schnell, weniger Context-Switch (HTML + CSS in einer Datei)
- Konsistent (Design-System eingebaut)
- Tree-Shaking → kleines Bundle
**Contra:**
- HTML voller Klassen — schwerer zu lesen
- Lernkurve eigene
- Versions-Lock-in mit Setup
**Faustregel:** für neue Projekte ausprobieren, vorhandene Projekte nicht zwanghaft migrieren.
---
<!-- _class: aufgabe -->
# Selbstlernen — Layout-Übung
1. **Flexbox-Spiel:** [flexboxfroggy.com](https://flexboxfroggy.com) — alle 24 Level durch.
2. **Grid-Spiel:** [cssgridgarden.com](https://cssgridgarden.com) — 28 Level durch.
3. **Eigene Karten-Galerie:** `repeat(auto-fit, minmax(...))` Pattern anwenden.
**Bonus:** Dark-Mode mit CSS-Variablen + `prefers-color-scheme`.
---
# Zusammenfassung
**Heute gelernt:**
- Box-Model + `box-sizing: border-box` global
- **Flexbox** für 1D-Layouts (Navigation, Karten-Reihen)
- **Grid** für 2D-Layouts (Seiten-Layout, Galerien)
- Mobile-First Media Queries + `clamp()` für fluid
- Transitions + Keyframes — `prefers-reduced-motion` respektieren
- CSS-Variablen für Theming
- Tailwind & Co. als moderner Framework-Stil
**Nächste Stunde:** Node.js — JavaScript außerhalb des Browsers.
+297 -220
View File
@@ -8,277 +8,354 @@ footer: "Michael Czechowski – SoSe 2026"
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
} pre code { background: transparent; color: inherit; }
section { code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
font-size: 1.4rem; a { color: var(--color-highlight); }
}
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 3 — Node.js Basics
## JavaScript außerhalb des Browsers
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 4 — Node.js: Scripting, Running and Building
--- ---
# Ressourcen zum Selbstlernen # Was ist Node.js?
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/) **Ryan Dahl, 2009.** JavaScript-Runtime auf V8 (Chrome's JS-Engine).
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/) Vorher: JS war Browser-only.
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/) Mit Node: JS auch als Server, CLI-Tools, Build-Skripte.
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
**Heute:** Node ist Standard-Backend für viele Web-Apps. Netflix, LinkedIn, NASA, Walmart.
--- ---
# Inhalt # Warum Node?
1. Node.js: was und warum | Vorteil | Was bedeutet das |
2. `node --check`, `node -e` (One-Liner) |---------|------------------|
3. Module: `require` / `import`, `__dirname` / `__filename` | **JS überall** | gleiche Sprache Frontend + Backend |
4. HTTP-Server (built-in) | **NPM** | größtes Package-Ökosystem (~3 Mio Packages) |
5. `fs` — Dateien lesen / schreiben | **Non-blocking I/O** | viele Verbindungen gleichzeitig |
6. `package.json` — Aufbau und Lifecycle | **Schnell genug** | V8 optimiert JS aggressiv |
7. npm Scripts | **Cross-platform** | Linux, macOS, Windows |
8. Module exportieren
--- ---
<!-- _class: invert --> # Node Versions-Manager
<!-- _backgroundColor: #000 -->
# Node.js – Scripting, Running and Building **fnm** (oder `nvm`, `volta`) — pro Projekt Node-Version.
--- ```sh
# Installieren (macOS/Linux)
brew install fnm # oder curl-script
# Node.js – Was ist das? # Aktuelle LTS
fnm install --lts
fnm use lts-latest # 24.x
- JavaScript Runtime (V8 Engine) # Pro Projekt
- Server-side JavaScript echo "24" > .nvmrc
- npm Package Manager fnm use # liest .nvmrc
- Ideal für: APIs, CLI Tools, Automation
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/nodejs-usecases.png)
---
# node --check und node -e
```bash
# Syntax check only
node --check script.js
# One-liner
node -e "console.log('Hallo Welt')"
``` ```
**Aktuell:** Node 24 LTS.
--- ---
# node -e Beispiele # Projekt initialisieren
```bash ```sh
# String mkdir mein-projekt && cd mein-projekt
node -e "console.log('Hello ' + process.argv[2])" Welt npm init -y # erzeugt package.json
# File reading (ESM via --input-type) # Abhängigkeiten installieren
node --input-type=module -e \ npm install express # → dependencies
"import { readFileSync } from 'node:fs'; \ npm install -D vitest # → devDependencies (nur dev)
console.log(readFileSync('data.json', 'utf8'))"
# JSON parsen # Skripte ausführen
node -e "console.log(JSON.parse(process.argv[1]))" '{"a":1}' npm run start
npm test
``` ```
--- `-y` = alle Defaults akzeptieren.
# Module laden – ESM (modern)
`"type": "module"` in `package.json` einschalten.
```javascript
// Eigene Datei (Endung .js erforderlich!)
import { add, greet } from "./utils.js";
// npm Modul (default + named import)
import express from "express";
import { Router } from "express";
// Built-in (mit "node:"-Präfix)
import { readFileSync } from "node:fs";
import { join } from "node:path";
```
<small>**Legacy CJS:** `const express = require("express")` — funktioniert noch, aber ESM ist heute Standard.</small>
--- ---
# Pfad-Auflösung in ESM # package.json
```javascript
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const configPath = join(__dirname, "..", "config.json");
```
<small>In ESM gibt es **kein** automatisches `__dirname` / `__filename` (anders als CJS). Über `import.meta.url` rekonstruieren.</small>
---
# HTTP Server mit Node.js (Built-in)
**ESM** (modern, `"type": "module"` in `package.json`):
```javascript
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("<h1>Hallo Welt!</h1>");
});
server.listen(3000, () => {
console.log("Server läuft auf Port 3000");
});
```
<small>**CommonJS** (legacy): `const http = require("http");` — funktioniert noch, aber neue Projekte nutzen ESM.</small>
---
# fs – Dateien lesen/schreiben
```javascript
import { readFile, readFileSync, writeFile } from "node:fs";
import { readFile as readFileAsync } from "node:fs/promises";
// Promise-API (empfohlen, modern)
const data = await readFileAsync("data.json", "utf8");
console.log(JSON.parse(data));
// Callback-API
readFile("data.json", "utf8", (err, data) => {
if (err) throw err;
console.log(JSON.parse(data));
});
// Sync (nur für Setup / Scripts)
const content = readFileSync("data.json", "utf8");
```
---
# package.json – Das Herzstück
```json ```json
{ {
"name": "mein-projekt", "name": "mein-projekt",
"version": "1.0.0", "version": "0.1.0",
"main": "index.js", "type": "module",
"scripts": { "scripts": {
"start": "node index.js", "dev": "node --watch src/server.js",
"dev": "node --watch index.js", "start": "node src/server.js",
"test": "node --test" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"express": "^4.18.0" "express": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.0.0" "vitest": "^2.0.0"
} }
} }
``` ```
--- `"type": "module"` aktiviert ESM.
# npm Scripts
```bash
# npm run <script>
npm run start
npm run dev
# Mit Argumenten
npm run build -- --production
# Lifecycle scripts
npm install → postinstall
npm start → start (direkt)
```
--- ---
# Node.js Module – exports (ESM) # ESM — modern
```javascript ```javascript
// utils.js — named exports // utils.js
export const add = (a, b) => a + b; export function greet(name) {
export const greet = (name) => `Hallo ${name}!`; return `Hallo ${name}`;
}
// utils.js — default export export const VERSION = "1.0";
export default {
add, // main.js
greet import { greet, VERSION } from './utils.js';
console.log(greet('Ada'), VERSION);
```
**Pflicht:** `.js`-Endung in import-Pfad.
---
# CommonJS — Legacy
```javascript
// utils.cjs
function greet(name) {
return `Hallo ${name}`;
}
module.exports = { greet };
// main.cjs
const { greet } = require('./utils.cjs');
```
**Wann CJS?** Alte Codebases, npm-Packages, die nicht migriert sind. **Default heute:** ESM.
---
# Built-in Modules
```javascript
import { readFile } from 'node:fs/promises';
import { createServer } from 'node:http';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
```
`node:` Prefix macht explizit klar — kein npm-Package.
---
<!-- _class: lead -->
# HTTP-Server
---
# Eingebauter HTTP-Server
```javascript
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hallo Welt');
});
server.listen(3000, () => {
console.log('Server läuft auf :3000');
});
```
Nackt — ohne Framework. **Funktioniert.**
---
# Express — der Klassiker
```javascript
import express from 'express';
const app = express();
app.use(express.json()); // parsed JSON-Body
app.use(express.static('public')); // statische Dateien
app.get('/', (req, res) => {
res.send('Hallo Welt');
});
app.post('/api/echo', (req, res) => {
res.json({ youSent: req.body });
});
app.listen(3000);
```
---
# REST mit Express
```javascript
const users = [{ id: 1, name: 'Ada' }];
app.get('/users', (req, res) => res.json(users));
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id == req.params.id);
user ? res.json(user) : res.sendStatus(404);
});
app.post('/users', (req, res) => {
const user = { id: users.length + 1, ...req.body };
users.push(user);
res.status(201).json(user);
});
```
---
# Alternative Frameworks
| Framework | Stärke |
|-----------|--------|
| **Express** | etabliert, größte Community |
| **Fastify** | schneller, schemas eingebaut |
| **Hono** | leichtgewichtig, Edge-deployable |
| **Koa** | von Express-Autoren, modern |
Für DHBW: **Express** oder **Hono**.
---
# Routing-Pattern
```javascript
import { Router } from 'express';
const userRouter = Router();
userRouter.get('/', (req, res) => { /* alle Users */ });
userRouter.get('/:id', (req, res) => { /* ein User */ });
userRouter.post('/', (req, res) => { /* User anlegen */ });
userRouter.put('/:id', (req, res) => { /* User update */ });
userRouter.delete('/:id', (req, res) => { /* User löschen */ });
app.use('/users', userRouter); // mountet unter /users/*
```
---
<!-- _class: lead -->
# Fetch in Node
---
# fetch — global ab Node 18
```javascript
// GET
const res = await fetch('https://api.github.com/users/torvalds');
const data = await res.json();
console.log(data.public_repos);
// POST
const post = await fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hallo' })
});
```
Genau wie im Browser. **Kein extra Package** (vorher: `node-fetch`, `axios`).
---
# Error-Handling
```javascript
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
return data;
} catch (error) {
console.error('API call failed:', error.message);
throw error; // re-throw für caller
}
```
`res.ok` ist `true` für 2xx-Statuscodes.
---
# Environment Variables
```javascript
// .env (gitignored!)
DATABASE_URL=postgres://...
API_KEY=sk-...
PORT=3000
// src/config.js
import 'dotenv/config';
export const config = {
port: process.env.PORT || 3000,
dbUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY,
}; };
``` ```
```javascript **Niemals** API-Keys ins Repo committen.
// nutzen
import { add, greet } from "./utils.js"; // named
import utils from "./utils.js"; // default
import utils, { add } from "./utils.js"; // beides
```
<small>**Legacy CJS:** `module.exports = { add, greet }` + `const { add } = require("./utils")`.</small> ---
<!-- _class: aufgabe -->
# Selbstlernen — Mini-API
1. **Projekt initialisieren:** `npm init -y`, ESM aktivieren
2. **Express installieren**
3. **REST-API** für eine Ressource (Books / Movies / TODO)
- `GET /items`, `GET /items/:id`, `POST /items`, `DELETE /items/:id`
4. **In-Memory-Store** als Array (echte DB kommt Kap 4)
5. **Mit Hoppscotch oder curl** testen
**Bonus:** `npm run dev` mit `--watch`-Flag.
---
# Zusammenfassung
**Heute gelernt:**
- Node.js als JS-Runtime außerhalb Browser
- **fnm + Node 24 LTS** als Versions-Setup
- **npm init**, package.json, scripts, dependencies
- **ESM** (import/export) als modernes Modul-System
- Built-in HTTP-Server + **Express** für REST
- **fetch** global ab Node 18
- **.env + dotenv** für Secrets
**Nächste Stunde:** async/await, Filesystem, Datenbank, Auth.
+291 -207
View File
@@ -8,289 +8,373 @@ footer: "Michael Czechowski – SoSe 2026"
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
} pre code { background: transparent; color: inherit; }
section { code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
font-size: 1.4rem; a { color: var(--color-highlight); }
}
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 4 — Node.js Advanced
## async · Files · Database · Auth · Errors
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 5 — Express API, CRUD und Middlewares
--- ---
# Ressourcen zum Selbstlernen <!-- _class: lead -->
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/) # Asynchron + Promises
- **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/)
--- ---
# Inhalt # Synchron vs. Asynchron
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
---
<!-- _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
**Synchron** (blockierend):
```javascript ```javascript
process.on("SIGINT", () => { const data = readFileSync('huge.txt'); // wartet
console.log("Received SIGINT. Closing server..."); console.log('weiter'); // erst danach
server.close(() => {
console.log("Server closed.");
process.exit(0);
});
});
// Signale
process.exit(0); // Erfolgreich
process.exit(1); // Fehler
``` ```
--- **Asynchron** (non-blocking):
# node.js Module System
```javascript ```javascript
// ES Modules (modern, "type": "module") const data = await readFile('huge.txt'); // wartet ohne blockieren
import express from "express"; console.log('weiter'); // erst danach
import { port } from "./src/config/constants.js";
import { readFileSync } from "node:fs";
``` ```
```javascript Node's Stärke: **non-blocking I/O** für viele parallele Operationen.
// 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 # Promise — Anatomie
```javascript ```javascript
// Node.js 18+ hat fetch eingebaut const promise = fetch('https://api.example.com');
const res = await fetch('https://api.example.com/data');
// 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(); const data = await res.json();
console.log(data);
// Ältere Versionen: node-fetch } catch (err) {
import fetch from 'node-fetch'; console.error(err);
}
``` ```
--- ---
# Web Server, Builder, Bundler # Sequenziell vs. Parallel
```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 ```javascript
import express from 'express'; // SEQUENZIELL — langsam (warten + warten + warten)
const app = express(); const a = await fetch('/api/a');
const b = await fetch('/api/b');
const c = await fetch('/api/c');
// Gesamtzeit: a + b + c
app.listen(port, () => { // PARALLEL — schnell (alle gleichzeitig starten)
console.log(`Server läuft auf Port ${port}`); const [a, b, c] = await Promise.all([
}); fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c'),
]);
// Gesamtzeit: max(a, b, c)
``` ```
--- ---
# Express – Middleware # 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 ```javascript
app.use(express.json()); const fastest = await Promise.race([
fetch('/api'),
app.use('/api', (req, res, next) => { new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000))
console.log('Time:', Date.now()); ]);
next();
});
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});
``` ```
--- ---
# Express – CRUD Routes <!-- _class: lead -->
# Filesystem
---
# fs/promises — Files lesen + schreiben
```javascript ```javascript
app.get('/api/:key', async (req, res) => { import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
const context = cache.get(req.params.key);
res.json({ context });
});
app.post('/api/:key', async (req, res) => { // Lesen
const payload = req.body; const text = await readFile('input.txt', 'utf-8');
res.json({ created: cache.set(req.params.key, payload) }); const buf = await readFile('image.png'); // Buffer
});
app.put('/api/:key', async (req, res) => { // Schreiben
const payload = req.body; await writeFile('output.txt', 'Hallo Welt');
res.json({ updated: cache.update(req.params.key, payload) }); await writeFile('data.json', JSON.stringify({ x: 1 }, null, 2));
});
app.delete('/api/:key', async (req, res) => { // Verzeichnis lesen
cache.remove(req.params.key); const files = await readdir('./uploads');
res.json({ deleted: req.params.key });
}); // Verzeichnis anlegen
await mkdir('./tmp', { recursive: true });
``` ```
--- ---
# Express – server.listen # Streams für große Dateien
```javascript ```javascript
const server = app.listen(port, () => { import { createReadStream, createWriteStream } from 'node:fs';
console.log(`Server läuft auf Port ${port}`); import { pipeline } from 'node:stream/promises';
});
server.on('error', (err) => { // 10 GB Logfile ohne RAM-Explosion verarbeiten
console.error('Server error:', err); await pipeline(
}); createReadStream('huge.log'),
// ... transform-stream ...
server.close(() => { createWriteStream('output.log')
console.log('Server geschlossen'); );
});
``` ```
Streams = Daten **stückweise** verarbeiten, nicht alles in RAM.
--- ---
# WebSockets <!-- _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 ```javascript
import { WebSocketServer } from 'ws'; import Database from 'better-sqlite3';
const db = new Database('app.db');
const wss = new WebSocketServer({ port: 8080 }); db.prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`).run();
wss.on('connection', (ws) => { // Insert
console.log('Client verbunden'); const insert = db.prepare('INSERT INTO users (name) VALUES (?)');
insert.run('Ada');
ws.on('message', (data) => { // Query
console.log('Empfangen:', data.toString()); const users = db.prepare('SELECT * FROM users').all();
ws.send('Pong'); 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 });
}); });
``` ```
--- ---
<!-- _header: '' --> <!-- _class: lead -->
<!-- _footer: '' -->
![bg fit](./assets/demos/ts-js-compilation.png)
# Authentifizierung
--- ---
<!-- _header: '' --> # Auth-Strategien
<!-- _footer: '' -->
![bg fit](./assets/demos/docker-orchestration.png) | 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**.
--- ---
<!-- _header: '' --> # Passwörter — *niemals plaintext*
<!-- _footer: '' -->
![bg fit](./assets/demos/test-automation-trophy.png) ```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.
+288 -279
View File
@@ -9,366 +9,375 @@ title: Testing
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
} pre code { background: transparent; color: inherit; }
section { code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
font-size: 1.4rem; a { color: var(--color-highlight); }
}
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 5 — Testing
## Unit · Integration · E2E · TDD
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 7 — Testing
--- ---
# Ressourcen zum Selbstlernen # Warum testen?
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/) | Vorteil | Was bedeutet das |
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/) |---------|------------------|
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/) | **Confidence** | Refactor ohne Angst |
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/) | **Regression-Schutz** | alter Bug taucht nicht wieder auf |
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/) | **Living Documentation** | Tests zeigen, was Code soll |
| **CI/CD-Voraussetzung** | automatisch vor jedem Deploy |
**„Untested Code is broken by design."**
--- ---
<!-- _class: invert --> # Test-Pyramide
<!-- _backgroundColor: #000 -->
# 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) /E2E\ wenige (langsam, fragil)
▲▲▲▲ Integration (class + real deps) /-----\
▲▲▲▲▲▲ Unit (class + mocked deps) / Integ \ einige (mittel)
▲▲▲▲▲▲▲▲ Static analysis (compiler, linter) /---------\
/ Unit \ viele (schnell, isoliert)
/-------------\
``` ```
- Oben: wenige, langsam, teuer, brüchig **Faustregel:** 70 % Unit, 20 % Integration, 10 % E2E.
- Unten: viele, schnell, billig, stabil
--- ---
# Test Automation V-Modell # Vitest — modernes JS-Testing
```sh
npm install -D vitest
``` ```
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 ```javascript
// cache.unit.test.js // utils.test.js
import { describe, it, expect } from "vitest"; import { describe, it, expect } from 'vitest';
import { Cache } from "./cache.js"; import { greet } from './utils.js';
describe("Cache", () => { describe('greet', () => {
it("creates and retrieves data with custom key", () => { it('returns greeting with name', () => {
const cache = new Cache(); expect(greet('Ada')).toBe('Hallo Ada');
const data = { user: "wolfgang", city: "stuttgart" }; });
const key = cache.create(data, "custom-key"); it('handles empty string', () => {
expect(greet('')).toBe('Hallo ');
expect(key).toBe("custom-key");
expect(cache.get("custom-key")).toEqual(data);
}); });
}); });
``` ```
--- ```sh
npx vitest
# Integration Tests – Eigenschaften npx vitest --coverage
```
- **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) # Test-Struktur (AAA)
```javascript ```javascript
// app.integration.test.js it('adds two numbers', () => {
import request from "supertest"; // ARRANGE — Setup
import { app, cache } from "./app.js"; const a = 2;
const b = 3;
test("full CRUD lifecycle with real cache", async () => { // ACT — die Operation
const result = add(a, b);
// ASSERT — Erwartung
expect(result).toBe(5);
});
```
**Eine Erwartung pro Test** (Faustregel).
---
# Vitest-Matchers
| Matcher | Was |
|---------|-----|
| `.toBe(x)` | strict equal (`===`) |
| `.toEqual(x)` | deep equal (Objects, Arrays) |
| `.toBeNull()` / `.toBeUndefined()` | |
| `.toBeTruthy()` / `.toBeFalsy()` | |
| `.toContain(item)` | Array enthält |
| `.toHaveLength(n)` | |
| `.toThrow()` | wirft Fehler |
| `.toMatchSnapshot()` | Snapshot-Test |
---
<!-- _class: lead -->
# TDD — Test-Driven Development
---
# TDD-Cycle: Red → Green → Refactor
1. **Red:** Test schreiben (schlägt fehl)
2. **Green:** minimaler Code, damit Test besteht
3. **Refactor:** Code säubern, Test bleibt grün
**Kent Beck, 2002.** Steile Lernkurve, aber stark fokussiert.
```javascript
// 1. Red — Test ohne Implementierung
it('isEven returns true for even', () => {
expect(isEven(4)).toBe(true);
});
// → ReferenceError: isEven is not defined
// 2. Green — minimale Lösung
function isEven(n) { return n % 2 === 0; }
// 3. Refactor — Edge Cases, Lesbarkeit
```
---
<!-- _class: lead -->
# Integration-Tests
---
# API testen mit Supertest
```javascript
import request from 'supertest';
import { app } from './app.js';
describe('GET /users', () => {
it('returns users array', async () => {
const res = await request(app).get('/users');
expect(res.status).toBe(200);
expect(res.body).toBeInstanceOf(Array);
});
});
describe('POST /users', () => {
it('creates a user', async () => {
const res = await request(app) const res = await request(app)
.post("/api/entries") .post('/users')
.send({ name: "integration-test", status: "active" }); .send({ name: 'Ada' });
expect(res.status).toBe(201); expect(res.status).toBe(201);
const { key } = res.body; expect(res.body.name).toBe('Ada');
});
expect(cache.keys()).toContain(key);
}); });
``` ```
--- ---
# End-to-End Tests (API) # Test-Datenbank
- **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 ```javascript
// index.e2e.test.js import { beforeAll, afterEach, afterAll } from 'vitest';
import { createServer } from "node:http"; import Database from 'better-sqlite3';
import { app } from "./app.js";
let server; let db;
const TEST_PORT = 8081;
const baseUrl = `http://localhost:${TEST_PORT}`;
beforeAll(async () => { beforeAll(() => {
server = createServer(app); db = new Database(':memory:'); // in-RAM, super schnell
await new Promise((r) => server.listen(TEST_PORT, r)); db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
}); });
afterAll(() => server.close()); afterEach(() => {
db.exec('DELETE FROM users'); // sauberer Start
});
test("complete user journey", async () => { afterAll(() => db.close());
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);
});
``` ```
--- `:memory:`-DB = jeder Test isoliert, blitzschnell.
# End-to-End Tests (Client)
- **Echter Browser** – Chromium / Firefox / WebKit
- **Echtes DOM** – Klicks, Tippen, Scrollen
- **Echtes Rendering** – CSS, async Operationen
- **Cross-Origin** – Client- und Server-Kommunikation
Tools: **Cypress**, **Playwright**
--- ---
# Cypress – Minimalbeispiel # Mocking
```javascript ```javascript
// cypress/e2e/counter.cy.js import { vi, it, expect } from 'vitest';
describe("Counter App", () => {
it("increments on click", () => { it('calls fetch with right URL', async () => {
cy.visit("http://localhost:3000"); const mockFetch = vi.spyOn(global, 'fetch').mockResolvedValue({
cy.contains("button", "Clicked 0 times").click(); ok: true,
cy.contains("button", "Clicked 1 times"); json: async () => ({ name: 'Ada' })
}); });
await getUserData(1);
expect(mockFetch).toHaveBeenCalledWith('https://api/users/1');
}); });
``` ```
```bash **Mock sparsam** — übermockt-Tests testen Mocks, nicht Code.
npx cypress open # GUI
npx cypress run # headless / CI
```
--- ---
# Playwright – Minimalbeispiel <!-- _class: lead -->
# E2E — End-to-End
---
# Playwright — moderne E2E
```sh
npm init playwright@latest
```
```javascript ```javascript
// tests/counter.spec.js import { test, expect } from '@playwright/test';
import { test, expect } from "@playwright/test";
test("counter increments", async ({ page }) => { test('login flow', async ({ page }) => {
await page.goto("http://localhost:3000"); await page.goto('http://localhost:3000');
await page.getByRole("button").click(); await page.getByLabel('Email').fill('ada@example.com');
await expect(page.getByRole("button")).toContainText("Clicked 1 times"); await page.getByLabel('Password').fill('secret123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Hallo Ada')).toBeVisible();
}); });
``` ```
```bash Browser tatsächlich öffnen + interagieren. **Realer User-Flow.**
npx playwright test
npx playwright test --ui
```
--- ---
# Coverage Reports # Playwright-Locators
```bash ```javascript
npx vitest run --coverage // Bevorzugt: rollenbasiert (A11y-konform!)
page.getByRole('button', { name: 'Submit' });
page.getByLabel('Username');
page.getByText('Welcome');
// Fallback: CSS-Selektor
page.locator('.submit-btn');
page.locator('#username');
``` ```
``` **Rollenbasierte Locators** finden Bugs, die A11y betreffen.
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 # E2E vs. Unit — Trade-offs
- **AAA**: Arrange · Act · Assert | | Unit | E2E |
- Tests sind **Dokumentation** – beschreibend benennen |--|------|-----|
- Ein Verhalten pro Test | Geschwindigkeit | ms | s |
- Keine Logik im Test (keine `if`/`for`) | Isolation | hoch | gering |
- **Test Doubles**: stub, mock, spy, fake | Realismus | gering | hoch |
- CI: Tests bei jedem Push | Wartung | wenig | viel |
| Wann | jede Funktion | kritische Flows |
E2E nicht für jede Detail-Logik — für **happy path + Login + Bezahlung**.
--- ---
# Live Coding <!-- _class: lead -->
Repo: `node-cache-api` # Coverage + CI
```bash ---
git clone https://github.com/nextlevelshit/node-cache-api
cd node-cache-api # Coverage-Report
npm install
npm test # unit + integration ```sh
npm run test:e2e # e2e npx vitest --coverage
``` ```
```
File | % Stmts | % Branch | % Funcs | Uncovered
-------------+---------+----------+---------+-----------
utils.js | 100 | 100 | 100 |
api.js | 75.3 | 60 | 80 | 42-48
auth.js | 91.2 | 85 | 100 | 15
```
**100 % ≠ keine Bugs.** Aber 0 % = sicher Bugs.
---
# Coverage-Ziel realistisch
| Code-Typ | Realistisch |
|----------|-------------|
| Pure Funktionen | 100 % |
| Business-Logik | 80-90 % |
| API-Routes | 70-80 % |
| UI-Komponenten | 60-70 % |
| Drittpartei-Wrapper | 0 % |
| Konfig-Files | 0 % |
**Faustregel:** über 80 % Gesamt = gut. Unter 50 % = gefährlich.
---
# CI: Tests auf jedem Push
```yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci
- run: npm test
- run: npm run test:e2e
```
Tests sind grün → PR mergebar. Sonst: rot, mergen verboten.
---
<!-- _class: aufgabe -->
# Selbstlernen — Tests schreiben
Erweitert eure API:
1. **3+ Unit-Tests** für eure Helper-Funktionen (`utils.test.js`)
2. **3+ Integration-Tests** für eure REST-Endpunkte (mit Supertest)
3. **In-Memory-DB** in `beforeAll`/`afterEach`
4. **1 E2E-Test** mit Playwright (z.B. Login-Flow)
5. **Coverage-Report** generieren
**Bonus:** GitHub-Actions-Workflow für CI.
---
# Zusammenfassung
**Heute gelernt:**
- **Test-Pyramide:** viele Unit, einige Integration, wenige E2E
- **Vitest** als modernes Test-Runner-Setup
- **AAA-Pattern** + Matchers + Lifecycle-Hooks
- **TDD-Cycle** Red → Green → Refactor
- **Supertest** für API-Integration-Tests
- **Playwright** für E2E mit rollenbasierten Locators
- **Coverage** als Werkzeug, nicht als Ziel
- **CI** mit GitHub-Actions
**Nächste Stunde:** TypeScript — Typen über JavaScript.
+280 -351
View File
@@ -9,423 +9,352 @@ title: TypeScript
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
} pre code { background: transparent; color: inherit; }
section { code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
font-size: 1.4rem; a { color: var(--color-highlight); }
}
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 6 — TypeScript
## Typen über JavaScript
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 8 — TypeScript
--- ---
<!-- _class: invert --> # Was ist TypeScript?
<!-- _backgroundColor: #000 -->
# TypeScript **Microsoft, 2012.** Aufsatz auf JavaScript mit **statischer Typprüfung.**
## JavaScript on Steroids - Schreibst TypeScript (`.ts`)
- Compiler (`tsc`) prüft Typen + erzeugt JavaScript (`.js`)
- Browser/Node führt das JS aus
**Heute:** Standard für mittlere + große JS-Projekte. ~95 % der React-Repos.
--- ---
# Inhalt # Warum TypeScript?
1. TypeScript: was und warum | Vorteil | Was bedeutet das |
2. `tsc` + `ts-node` |---------|------------------|
3. `tsconfig.json` | **Fehler vor Laufzeit** | Compiler fängt typos, falsche Aufrufe |
4. Type Primitives | **Bessere IDE** | Autocompletion, Refactoring, Jump-to-Definition |
5. Union Types, Type Alias, Interface | **Selbst-Doku** | Typ-Signaturen sind ausführbare Doku |
6. Enum, Generics, Type Casting | **Refactoring sicher** | Compiler findet alle Auswirkungen |
7. Compilation: types stripped
8. Decorators + reflect-metadata **Nachteil:** Build-Step + Lernkurve.
--- ---
# TypeScript – Was? # Setup
- **Superset von JavaScript** (Microsoft, 2012) ```sh
- Jede `.js` lässt sich zu `.ts` umbenennen npm install -D typescript @types/node tsx
- **Type Errors zur Compile-/Transpile-Zeit** npx tsc --init # tsconfig.json erzeugen
- 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:** `tsx` = TypeScript direkt ausführen ohne Compile-Step (Dev).
```bash
npx ts-node index.ts # statt: node index.js
```
**Build:** ```json
```bash // package.json
npx tsc # kompiliert nach ./dist {
node dist/index.js "scripts": {
"dev": "tsx --watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
}
}
``` ```
--- ---
# tsconfig.json # tsconfig.json — Wichtigste
```json ```json
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2022", "target": "ES2022",
"module": "commonjs", "module": "NodeNext",
"moduleResolution": "node", "moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true, "strict": true,
"esModuleInterop": true, "noImplicitAny": true,
"sourceMap": true, "strictNullChecks": true,
"emitDecoratorMetadata": true, "outDir": "dist",
"experimentalDecorators": true "rootDir": "src"
}, }
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
} }
``` ```
**`strict: true`** = alle strengen Checks an. **Pflicht** für neue Projekte.
--- ---
# Type Primitives # Basis-Typen
```typescript ```typescript
let loading: boolean = false; let name: string = "Ada";
let count: number = 42; let age: number = 56;
let pi: number = 3.14; let admin: boolean = true;
let name: string = "DHBW ist fetzig!"; let nothing: null = null;
let empty: null = null; let unset: undefined = undefined;
let missing: undefined = undefined;
function log(msg: string): void { let hobbies: string[] = ["math", "chess"];
console.log(msg); let tuple: [string, number] = ["Ada", 56];
let anyValue: any = "vermeiden"; // KEIN typ-check
let unknownValue: unknown = await fetch(); // sicherer als any
```
`any` deaktiviert TS-Schutz — vermeiden.
---
# Object-Typen + Interfaces
```typescript
// inline
function greet(user: { name: string; age?: number }) {
return `Hallo ${user.name}`;
} }
```
`any`, `unknown`, `never`, `void` sind weitere TS-Spezialtypen. // Interface
---
# Union Types
```typescript
let id: number | string;
id = 101; // ok
id = "101"; // ok
id = true; // TypeError (nicht erlaubt)
```
---
# 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 { interface User {
id: number;
name: string; name: string;
email?: string; // optional age?: number; // optional
readonly createdAt: Date; // schreibgeschützt readonly id: number; // schreibgeschützt
greet(msg: string): void;
} }
const u: User = { const ada: User = { id: 1, name: "Ada", age: 56 };
id: 1,
name: "Lisa", // Type-Alias (Interface mit anderem Namen, mehr Flexibilität)
createdAt: new Date(), type Status = "pending" | "active" | "deleted";
greet(msg) { console.log(msg); }
};
``` ```
--- ---
# Interface vs Type # Function-Typen
**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 ```typescript
enum Direction { // Argument + Return
Up = 1, function add(a: number, b: number): number {
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<T> {
private readonly value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numbers = new GenericContainer<number>(10);
const strings = new GenericContainer<string>("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 = (<string>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.
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# 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 = <T extends Function>(target: T) => T | void;
type PropertyDecorator = (
target: Object,
propertyKey: string | symbol
) => void;
type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | 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; return a + b;
} }
}
new Calculator().add(2, 3); // logs: → add([2,3]) // Arrow + ausgeschrieben
const multiply: (a: number, b: number) => number = (a, b) => a * b;
// async-Funktionen geben Promise zurück
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/users/${id}`);
return res.json();
}
``` ```
--- ---
# Reflect: Meta-Daten lesen # Union + Intersection
```typescript ```typescript
import "reflect-metadata"; // Union — "ODER"
type Id = string | number;
function lookup(id: string | number) { /* ... */ }
Reflect.defineMetadata("role", "admin", target); // Discriminated Union
Reflect.hasMetadata("role", target); // true type Shape =
Reflect.getMetadata("role", target); // "admin" | { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function area(s: Shape) {
if (s.kind === "circle") return Math.PI * s.radius ** 2;
return s.size ** 2; // TS weiß: hier ist's square
}
// Intersection — "UND"
type Admin = User & { permissions: string[] };
``` ```
Frameworks (NestJS, TypeORM) nutzen das, um zur **Runtime** Type-Informationen zu rekonstruieren – z. B. für DI, ORM-Mapping, Validierung.
--- ---
# Anwendung: NestJS-Beispiel # Generics — typ-flexible Funktionen
```typescript ```typescript
@Controller("users") // T ist ein Platzhalter
export class UsersController { function first<T>(arr: T[]): T | undefined {
constructor(private users: UsersService) {} return arr[0];
@Get(":id")
findOne(@Param("id") id: string) {
return this.users.find(+id);
} }
first([1, 2, 3]); // T = number
first(["a", "b"]); // T = string
// Generic-Constraints
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "hi"); // OK
longest([1, 2], [3, 4, 5]); // OK
longest(5, 10); // FEHLER: number hat kein length
```
---
# Built-in Generic-Typen
```typescript
// Array<T> = T[]
const nums: Array<number> = [1, 2, 3];
// Promise<T>
async function getUser(): Promise<User> { /* ... */ }
// Partial<T> — alle Properties optional
type UserUpdate = Partial<User>;
// Required<T> — alle Pflicht
// Pick<T, K> — nur ausgewählte
// Omit<T, K> — ohne ausgewählte
type PublicUser = Omit<User, "password">;
// Record<K, V> — Dictionary
const config: Record<string, string> = { host: "...", port: "8080" };
```
---
# Typ-Inference
TS errät den Typ oft selbst — **nicht überall annotieren.**
```typescript
let name = "Ada"; // automatisch: string
const numbers = [1, 2, 3]; // automatisch: number[]
function double(n: number) { // Return-Typ inferiert: number
return n * 2;
}
// Wann annotieren?
// - Funktion-Args (immer)
// - Public-API Return-Typen (Pflicht für Library)
// - wenn Inference falsch oder unklar
```
---
# Typen für REST-API
```typescript
// Schema einmal definieren
interface CreateUserBody {
name: string;
email: string;
}
interface UserResponse {
id: number;
name: string;
email: string;
createdAt: string;
}
// Express-Route typisiert
import type { Request, Response } from 'express';
app.post('/users', (req: Request<{}, {}, CreateUserBody>, res: Response<UserResponse>) => {
const { name, email } = req.body; // typsicher
// ...
});
```
---
# Zod — Runtime-Validation
TypeScript prüft zur Compile-Time. **Was kommt aus dem Netz?**
```typescript
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>; // Typ aus Schema
app.post('/users', (req, res) => {
const result = UserSchema.safeParse(req.body);
if (!result.success) return res.status(400).json(result.error);
const user = result.data; // typsicher + validated
});
```
---
# `any` vs `unknown` vs `never`
```typescript
let a: any = "test";
a.toFixed(); // OK — TS prüft NICHTS
let u: unknown = "test";
u.toFixed(); // FEHLER — erst type-narrow
if (typeof u === 'number') u.toFixed(); // OK
function fail(msg: string): never {
throw new Error(msg); // kommt nie zurück
} }
``` ```
`@Controller`, `@Get`, `@Param` sind Decorators – Metadaten ⇒ Routing. **`unknown` statt `any`** — sicherer Default für fremde Daten.
---
# Best Practices
- **`strict: true`** in tsconfig — keine Diskussion
- **Argument-Typen** annotieren, Return-Typen nur public-API
- **Interfaces** für Objekt-Strukturen, **Type-Aliases** für Unions
- **`unknown`** statt `any`
- **Zod / Valibot** für Runtime-Validation
- **Typen nicht doppelt:** ein Schema, davon Typ ableiten (`z.infer`)
- **CI-Step:** `tsc --noEmit` muss grün sein
---
<!-- _class: aufgabe -->
# Selbstlernen — eure API zu TS migrieren
1. **TypeScript installieren:** `npm install -D typescript @types/node @types/express tsx`
2. **tsconfig.json** mit `strict: true`
3. **`.js` → `.ts`** umbenennen, fehlende Typen ergänzen
4. **Zod-Schema** für POST-Bodies
5. **CI-Step:** `tsc --noEmit` in Tests
**Bonus:** Drizzle-ORM verwenden (TypeScript-first).
---
# Zusammenfassung
**Heute gelernt:**
- TS als statischer Typ-Aufsatz auf JS
- **`strict: true`** als Baseline
- Basis-Typen, Interfaces, Type-Aliases
- **Union** + **Generics** als zentrale Konzepte
- Built-in Generics (Partial, Omit, Record)
- **Typ-Inference** nutzen — nicht über-annotieren
- **Zod** für Runtime-Validation
- `unknown` statt `any`
**Nächste Stunde:** Docker — Container für reproducible Setups.
+268 -232
View File
@@ -9,339 +9,375 @@ title: Docker und Service Orchestration
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
} pre code { background: transparent; color: inherit; }
section { code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
font-size: 1.4rem; a { color: var(--color-highlight); }
}
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 7 — Docker
## Container für reproducible Setups
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 9 — Docker, Proxies and DBs
--- ---
<!-- _class: invert --> # „Auf meinem Rechner läuft's"
<!-- _backgroundColor: #000 -->
# Docker Das klassische Software-Problem:
## Service Orchestration - Entwickler: Node 22 + Postgres 16 lokal
- Server: Node 18 + Postgres 14
- → bricht beim Deploy
**Lösung:** App + Abhängigkeiten als **Container** verpacken.
--- ---
# Inhalt # Container vs. VM
1. Architektur: Reverse Proxy + API + DB | | Container | Virtual Machine |
2. Container vs VM |--|-----------|-----------------|
3. `Dockerfile` | Startzeit | ms | min |
4. `compose.yml` | Größe | MB | GB |
5. Docker CLI / Compose Befehle | Kernel | shared (Host-Linux) | eigener |
6. Live Demo: `dhbw-docker` | Isolation | Prozess-Level | Hardware-Level |
| Overhead | minimal | hoch |
Container = leichtgewichtige Prozess-Isolation.
--- ---
# Architektur # Docker — die Idee
``` - **Image** = Schnappschuss (App + Files + OS-Layer)
Browser (localhost:10007) - **Container** = laufende Instanz eines Images
│ - **Dockerfile** = Anleitung zum Image-Bauen
▼ - **Registry** = Image-Speicher (Docker Hub, GHCR)
┌──────────────────┐
│ Reverse Proxy │ (nginx / traefik) ```sh
└─────────┬────────┘ docker pull node:24
┌────┴────┐ docker run node:24 node --version
▼ ▼
┌─────────┐ ┌────────┐
│ Web │ │ API │
│ App │ │ (Node) │
└────┬────┘ └────┬───┘
└─────┬──────┘
▼
┌────────┐
│ DB │ (Postgres)
└────────┘
``` ```
--- ---
# Container vs VM # Dockerfile — Hallo Welt
| | 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 ```dockerfile
FROM node:lts-slim AS builder FROM node:24-alpine
WORKDIR /app WORKDIR /app
COPY ./package*.json ./ COPY package*.json ./
RUN npm ci --no-audit --no-fund --no-progress --loglevel=error RUN npm ci
COPY ./ ./ COPY . .
ENV NODE_ENV="production" EXPOSE 3000
CMD ["node", "src/server.js"]
EXPOSE 8080
CMD ["npm", "start"]
``` ```
**`npm ci`** statt `npm i` für reproduzierbare Builds. ```sh
docker build -t mein-app .
docker run -p 3000:3000 mein-app
```
---
# Dockerfile-Schichten verstehen
Jedes `RUN`/`COPY`/`ADD` = neue Schicht.
```dockerfile
# Schlecht — npm install nach JEDER Code-Änderung
COPY . .
RUN npm ci
# Gut — package.json zuerst, npm install gecached
COPY package*.json ./
RUN npm ci
COPY . .
```
**Build-Cache** spart Minuten.
--- ---
# Multi-Stage Build # Multi-Stage Build
```dockerfile ```dockerfile
FROM node:lts-slim AS builder # Stage 1: Build mit allem
FROM node:24-alpine AS builder
WORKDIR /app WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm ci && npm run build
FROM node:lts-slim AS runtime # Stage 2: nur Runtime
FROM node:24-alpine
WORKDIR /app WORKDIR /app
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./ COPY package*.json ./
EXPOSE 8080 EXPOSE 3000
CMD ["node", "dist/index.js"] CMD ["node", "dist/server.js"]
``` ```
→ kleines Final-Image, keine Build-Tools im Runtime. Finales Image: kleiner, sicherer (kein Build-Tools drin).
---
# Image-Größen reduzieren
| Base | Größe |
|------|------:|
| `node:24` | ~1.1 GB |
| `node:24-slim` | ~280 MB |
| `node:24-alpine` | **~180 MB** |
| Distroless | ~150 MB |
**`alpine`** = Alpine Linux, sehr klein. **Distroless** = nur Runtime, kein Shell.
--- ---
# .dockerignore # .dockerignore
Verhindert dass Müll ins Image kommt:
``` ```
node_modules node_modules
dist
.git .git
.env .env
*.log *.log
coverage build/
dist/
.DS_Store
README.md
.github/
``` ```
Spart Build-Kontext und verhindert Geheimnis-Leaks. Schneller Build, kleineres Image, weniger Secrets im Image.
--- ---
# compose.yml – Services <!-- _class: lead -->
# docker compose
---
# Multi-Container — die Realität
Eine App braucht oft:
- App-Server
- Datenbank
- Cache (Redis)
- Reverse Proxy
**docker compose** orchestriert das.
---
# compose.yml — Beispiel
```yaml ```yaml
name: dhbw-docker-app
services: services:
api: app:
build: ./api build: .
container_name: dhbw-api
environment:
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_HOST: db
DATABASE_PORT: 5432
ports: ports:
- "10000:8080" - "3000:3000"
networks: [net, data] environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on: depends_on:
db: - db
condition: service_healthy
```
---
# compose.yml – Datenbank + Proxy
```yaml
db: db:
image: postgres:16-alpine image: postgres:16-alpine
environment: environment:
POSTGRES_USER: ${DATABASE_USERNAME} POSTGRES_USER: user
POSTGRES_PASSWORD: ${DATABASE_PASSWORD} POSTGRES_PASSWORD: pass
POSTGRES_DB: ${DATABASE_NAME} POSTGRES_DB: app
volumes: volumes:
- dbdata:/var/lib/postgresql/data - db-data:/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: volumes:
dbdata: db-data:
networks:
net:
data:
``` ```
--- ---
# Docker Compose – Befehle # compose-Commands
```bash ```sh
docker compose -p "dhbw-docker-app" \ docker compose up # alles starten (Foreground)
-f compose.yml \ docker compose up -d # detached (background)
--env-file .env \ docker compose down # alles stoppen + entfernen
up \ docker compose down -v # ... und Volumes löschen
--build \
--remove-orphans \
--force-recreate
```
| Flag | Bedeutung | docker compose logs -f app # Logs streamen
|------|-----------| docker compose exec app sh # Shell im laufenden Container
| `-p` | Projektname | docker compose ps # Status sehen
| `-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 # Networking in compose
Standard: alle Services im selben `default`-Netzwerk.
```yaml ```yaml
api: services:
healthcheck: app:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"] environment:
interval: 10s # `db` ist der Service-Name → DNS-Hostname
timeout: 3s DATABASE_URL: postgres://...@db:5432/app
retries: 3
start_period: 5s
``` ```
`depends_on: { condition: service_healthy }` wartet, bis Health passt. **Service-Namen werden zu Hostnamen.** Kein localhost zwischen Containern.
--- ---
# Networks: Segmentierung # Volumes — Daten überleben
- **`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 ```yaml
volumes: volumes:
dbdata: # benannt db-data: # named volume (managed by Docker)
- ./code:/app # bind mount (dev)
- /app/node_modules # anonymous (überschreibt bind) services:
db:
volumes:
- db-data:/var/lib/postgresql/data # named
- ./backups:/backups # bind mount
``` ```
`docker compose down` ohne `-v` lässt Volumes leben. **Named Volumes** für DB-Daten. **Bind Mounts** für lokale Config.
--- ---
# Live Demo # Environment + Secrets
Repo: https://github.com/nextlevelshit/dhbw-docker ```yaml
services:
```bash app:
git clone https://github.com/nextlevelshit/dhbw-docker env_file: .env # ganze .env-Datei laden
cd dhbw-docker environment:
cp .env.example .env NODE_ENV: production # einzeln setzen
docker compose up --build DATABASE_URL: ${DATABASE_URL} # vom Host
``` ```
→ http://localhost:10007 **Secrets nicht ins compose.yml.** Lieber `.env` (gitignored) oder Docker Secrets.
--- ---
# 100 Concepts of Docker (Exkurs) <!-- _class: lead -->
- Image · Container · Volume · Network # CI/CD
- Dockerfile · Layer Cache · BuildKit
- Compose · Stack · Swarm · Kubernetes
- Registry · Tag · Digest
- Dev/Prod-Parity (12-Factor)
- Secrets · Configs
- Logging Drivers
- Health Checks · Restart Policies
---
# Docker in GitHub Actions
```yaml
name: Build + Push
on:
push:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: user/app:latest
```
---
# Container-Registry
| Registry | Stärke |
|----------|--------|
| **Docker Hub** | Standard, kostenlos für Public |
| **GHCR** (GitHub) | mit Repo verbunden |
| **GitLab Registry** | mit Repo verbunden |
| **AWS ECR** | für AWS-Hosting |
```sh
# Tag + Push
docker tag mein-app ghcr.io/user/mein-app:v1.0.0
docker push ghcr.io/user/mein-app:v1.0.0
```
---
# Health-Checks
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
```
```javascript
// In Express
app.get('/health', (req, res) => res.json({ ok: true }));
```
Orchestrators (k8s, ECS, Coolify) entscheiden via HealthCheck, ob neu starten.
---
# Best Practices
- **`alpine`** oder **distroless** als Base
- **Multi-stage builds** für kleinere Images
- **`.dockerignore`** anlegen
- **Non-root user** im Container (`USER node`)
- **`COPY` nach `RUN npm ci`** für Cache-Hit
- **Tags statt `latest`** für Reproducibility
- **Health-Checks** für Orchestration
- **Secrets via env, niemals im Image**
---
<!-- _class: aufgabe -->
# Selbstlernen — App dockerizen
1. **Dockerfile** für eure Node-API (multi-stage)
2. **.dockerignore** mit node_modules, .env, .git
3. **compose.yml** mit App + Postgres
4. **`docker compose up`** lokal starten
5. **Health-Check** auf `/health`-Endpoint
**Bonus:** GitHub-Actions-Workflow, der Image bei jedem Push baut.
---
# Zusammenfassung
**Heute gelernt:**
- Container vs. VM — Prozess-Isolation, leicht + schnell
- **Dockerfile** mit `FROM`, `COPY`, `RUN`, `CMD`
- **Multi-stage Builds** für kleine Production-Images
- **alpine** + Distroless als Base
- **docker compose** für Multi-Container-Setups
- **Volumes** für persistente Daten
- **Service-Namen als Hostnamen** im compose-Netzwerk
- **CI/CD** mit GitHub Actions + Registry
**Nächste Stunde:** Best Practices — Git, README, SOLID, 12-Factor.
+385 -261
View File
@@ -9,353 +9,477 @@ title: Best Practices
--- ---
<style> <style>
:root { :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
--color-foreground: #1a1a2e; section.invert { --color-foreground: #fff; }
--color-highlight: #d63384; section { font-size: 1.4rem; }
--color-dimmed: #4a4a6a; h1 { color: #a02060; }
} section.invert h1 { color: #fff; }
section.invert { h2 { color: #1f2937; }
--color-foreground: #fff; pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
} pre code { background: transparent; color: inherit; }
section { code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
font-size: 1.4rem; a { color: var(--color-highlight); }
}
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> </style>
<!-- _class: invert --> <!-- _class: lead -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
# Web Engineering # Kapitel 8 — Best Practices
## Git · README · SOLID · 12-Factor · SemVer
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 10 — Wrap-up & Projektsupport
--- ---
<!-- _class: invert --> <!-- _class: lead -->
<!-- _backgroundColor: #000 -->
# Best Practices # Git-Hygiene
## Wrap-up & Projektsupport
--- ---
# Inhalt # Sinnvolle Commits
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
```
<type>: <Subject>
<blank>
<Body>
<blank>
<Footer>
```
**Type:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
**Subject:** Imperativ, ≤50 Zeichen, klein, kein Punkt
**Body:** **Was** und **Warum** – nicht **Wie**
**Footer:** Issue-Refs, Breaking Changes
---
# Commit-Beispiele
**Gut:**
```
feat: add user logout endpoint
Allows authenticated clients to invalidate their session token
on the server side instead of only deleting the cookie.
Closes #142
```
**Schlecht:** **Schlecht:**
``` ```
fixed stuff fix
WIP wip
update
asdf asdf
final
final2
final-final
``` ```
→ https://udacity.github.io/git-styleguide/ **Gut:**
```
fix(auth): handle expired JWT correctly
feat(api): add /users/:id/posts endpoint
docs(readme): update Docker setup instructions
test(utils): cover edge cases in greet()
```
**Eine Commit-Message = Was + Warum, nicht Wie.**
--- ---
# Conventional Commits # Conventional Commits
``` ```
feat(auth): add OAuth2 login <type>(<scope>): <subject>
fix(api): return 404 instead of 500 on missing user
docs: update README install steps [optional body]
chore(deps): bump express from 4.18 to 4.19
BREAKING CHANGE: drop Node 16 support [optional footer]
``` ```
Vorteile: automatische Changelogs, automatische Version-Bumps (`semantic-release`). **Types:**
- `feat:` neue Funktion
- `fix:` Bugfix
- `docs:` Doku
- `refactor:` Code-Restrukturierung
- `test:` Tests
- `chore:` Build/Tools
- `perf:` Performance
- `style:` Formatierung
Automatisches Changelog + SemVer-Bump möglich.
--- ---
# 12 Factor App # .gitignore — was nie rein gehört
Methodik für moderne, skalierbare SaaS-Apps – https://12factor.net/ ```
# Dependencies
node_modules/
.pnpm-store/
| # | Faktor | # Build outputs
|---|--------| dist/
| 1 | Codebase – ein Repo, viele Deploys | build/
| 2 | Dependencies – explizit deklariert (`package.json`) | .next/
| 3 | Config – via Environment-Variablen | .cache/
| 4 | Backing Services – als Attached Resources |
| 5 | Build, Release, Run – getrennt | # Secrets
| 6 | Processes – stateless | .env
.env.local
*.pem
*.key
# Editor + OS
.vscode/
.idea/
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
```
Vorlagen: github.com/github/gitignore
--- ---
# 12 Factor (2/2) # Branches + PRs
| # | Faktor | ```sh
|---|--------| # Neuer Feature-Branch
| 7 | Port Binding – self-contained, exportiert HTTP | git checkout -b feat/user-profile
| 8 | Concurrency – horizontal skalieren via Prozesse |
| 9 | Disposability – schneller Start, Graceful Shutdown | # Arbeit, Commits
| 10 | Dev/Prod Parity – möglichst gleiche Umgebung | git add .
| 11 | Logs – als Event-Streams (`stdout`) | git commit -m "feat(profile): add avatar upload"
| 12 | Admin Processes – als One-Off |
# Push + PR
git push -u origin feat/user-profile
# → PR via GitHub/Gitea-UI
# Nach Merge: aufräumen
git checkout main
git pull
git branch -d feat/user-profile
```
**Faustregel:** kurzlebige Branches (Tage, nicht Monate).
--- ---
# Config via ENV <!-- _class: lead -->
# README
---
# README — das Visitenkarten-Dokument
Wenn jemand euer Projekt im Browser sieht: **die README ist die erste und oft einzige Berührung.**
Schlechte README = Repo nicht verwendet.
**Eine gute README beantwortet:**
1. Was tut das Projekt?
2. Wie installiere ich es?
3. Wie starte ich es?
4. Wie teste ich es?
5. Wie deploye ich es?
6. Lizenz?
---
# README-Template
```markdown
# Mein Projekt
> Eine Zeile Beschreibung.
## Features
- ✓ etwas
- ✓ etwas anderes
## Setup
```sh
git clone https://...
cd projekt
cp .env.example .env
npm install
npm run dev
```
## Tests
```sh
npm test
npm run test:e2e
```
## Stack
Node 24, Express, SQLite, Vitest.
## License
MIT
```
---
<!-- _class: lead -->
# SOLID — OO-Prinzipien
---
# SOLID — die fünf
| | Prinzip | Bedeutung |
|--|---------|-----------|
| **S** | Single Responsibility | jede Klasse/Modul = eine Verantwortung |
| **O** | Open / Closed | offen für Erweiterung, geschlossen für Änderung |
| **L** | Liskov Substitution | Subtyp ersetzbar durch Basistyp |
| **I** | Interface Segregation | kleine, fokussierte Interfaces |
| **D** | Dependency Inversion | abhängig von Abstraktionen |
Robert C. Martin („Uncle Bob"), 2000er.
---
# S — Single Responsibility
```javascript ```javascript
// schlecht // Schlecht — alles in einer Klasse
const db = "postgres://user:secret@prod-db:5432/app"; class UserManager {
validate() { /* ... */ }
saveToDb() { /* ... */ }
sendEmail() { /* ... */ }
generateReport() { /* ... */ }
}
// gut // Besser — getrennte Verantwortlichkeiten
const db = process.env.DATABASE_URL; class UserValidator { /* ... */ }
class UserRepository { /* ... */ }
class EmailService { /* ... */ }
class ReportGenerator { /* ... */ }
``` ```
```bash ---
# .env (NICHT committen!)
DATABASE_URL=postgres://user:secret@db:5432/app # D — Dependency Inversion
NODE_ENV=production
PORT=8080 ```javascript
// Schlecht — direkt von konkreter Klasse
class OrderService {
charge(order) {
const stripe = new StripeClient();
stripe.charge(order.amount);
}
}
// Besser — Abhängigkeit injizieren
class OrderService {
constructor(paymentProvider) {
this.payment = paymentProvider;
}
charge(order) {
this.payment.charge(order.amount);
}
}
new OrderService(new StripeClient()); // im Production
new OrderService(new MockClient()); // im Test
``` ```
`.env.example` mit Dummy-Werten committen. ---
<!-- _class: lead -->
# 12-Factor App
--- ---
# Make It Work · Right · Fast # 12-Factor — die wichtigsten
``` | # | Faktor | Was |
1. Make It Work → MVP, hässlich aber funktional |---|--------|-----|
2. Make It Right → Refactor, Tests, Clean Code | 1 | Codebase | ein Repo pro App, mehrere Deploys |
3. Make It Fast → Profiling, Optimierung | 2 | Dependencies | explizit deklariert (package.json) |
| 3 | **Config** | über Environment Variables, nicht im Code |
| 4 | Backing Services | als Resources behandeln (z.B. DB-URL) |
| 6 | **Processes** | stateless |
| 9 | Disposability | schnell startbar + stoppbar |
| 12 | Admin Processes | als One-Off im selben Stack |
Heroku, 2011. Heute Industrie-Standard für Cloud-Apps.
---
# Factor 3 — Config in env
```javascript
// SCHLECHT — Hardcoded
const db = new Pool({ host: 'localhost', user: 'dev' });
// GUT — aus env
const db = new Pool({
connectionString: process.env.DATABASE_URL,
});
``` ```
Don't optimize what doesn't work. `.env` lokal, **Environment-Variablen** auf Server. Gleicher Code überall.
Don't refactor what isn't working yet.
→ https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
--- ---
# KISS · DRY · YAGNI · SOLID # Factor 6 — Stateless Processes
- **KISS** – Keep It Simple, Stupid **Schlecht:** Session-State im RAM.
- **DRY** – Don't Repeat Yourself ```javascript
- **YAGNI** – You Aren't Gonna Need It const sessions = new Map(); // pro Server-Instanz!
- **SOLID**: ```
- **S**ingle Responsibility → Load-Balancer mit 2 Servern: User-Session geht verloren beim 2. Request.
- **O**pen/Closed
- **L**iskov Substitution
- **I**nterface Segregation
- **D**ependency Inversion
→ Faustregeln, keine Dogmen. **Gut:** State in DB / Redis.
```javascript
await redis.set(`session:${id}`, JSON.stringify(data));
```
Server-Restart → kein Datenverlust. Horizontale Skalierung möglich.
--- ---
# Präsentation – Aufbau <!-- _class: lead -->
- **Einstieg** mit aktuellem Bezug (optional) # SemVer — semantisches Versioning
- **Problemdefinition**: welches Bedürfnis / welches Problem?
- Was hat **blockiert**, wie gelöst?
- **Geschichte mit Spannungsbogen**
- Vorher klären: Fragen während oder nach der Präsentation?
--- ---
# Präsentation – Pitfalls # SemVer — Format
- Am Thema vorbeireden ```
- Folien vorlesen MAJOR.MINOR.PATCH
- Live-Demo ohne Backup-Video 1 . 4 . 3
- "Sieht man hier nicht so gut, aber..." ```
- Zu viel Text pro Folie
- Keine klare Botschaft pro Slide
→ **1 Folie = 1 Aussage.** | Teil | Bump bei |
|------|----------|
| **MAJOR** | breaking change (API-incompatible) |
| **MINOR** | neues Feature (rückwärts-kompatibel) |
| **PATCH** | Bugfix (rückwärts-kompatibel) |
**Pre-Release:** `1.0.0-beta.1`, `1.0.0-rc.2`.
--- ---
# Feedback – Sandwich-Methode # SemVer in package.json
1. **Eröffnung** — Lob / positive Beobachtungen ```json
2. **Kern** — Kritik + konkreter Verbesserungsvorschlag {
3. **Abschluss** — Lob / wertschätzender Ausklang "dependencies": {
"express": "^5.0.0", // ≥ 5.0.0, < 6.0.0
"lodash": "~4.17.0", // ≥ 4.17.0, < 4.18.0
"react": "5.2.1", // exakt
"vite": "*" // jede (vermeiden)
}
}
```
**Pitfalls:** **Faustregel:** `^` (Caret) ist Default — major-version-lock.
- Am Thema vorbeireden
- Bei Unklarheiten keine Fragen stellen
- Subjektives Feedback ("Farbe gefällt mir nicht")
→ **Konkret, sachlich, umsetzbar.**
--- ---
# Prüfungsleistung – Recap <!-- _class: lead -->
**Grundanforderungen (75 Punkte):** # Code-Reviews
| Punkte | Kriterium |
|--------|-----------|
| 20 | Idee, Konzeption, Planung |
| 5 | Plattformunabhängigkeit |
| 25 | Clean Code (KISS, SOLID, DI, Testing, Error Handling) |
| 15 | Präsentation |
| 10 | Dokumentation (README, etc.) |
**Zusatzpunkte (max. 10):** TypeScript, Docker, Dev/Prod-Parity, `.env`, npm-Publish, Domain, HTTPS, Responsive Design.
→ https://librete.ch/DHBW/pruefungsleistung
--- ---
# Abgabe + Termine # Constructive Code-Reviews
- **Code-Upload:** bis 27.07. **Geben:**
- **Präsentation:** 17.07. (in Gruppen, ~10 Min) - Konkret: Zeilennummern + Beispiel
- Pitch (~1 Min) - Wertschätzend: gut gemachte Stellen erwähnen
- Agenda - Fragend statt fordernd: „Wäre `X` hier robuster?"
- Idee / Problem - Klar zwischen MUST/SHOULD/CONSIDER unterscheiden
- Learnings & Pitfalls
- "It is what it is" – Doku **Nehmen:**
- Egoless: Code ≠ Person
- Frage zurück bei Unklarheit
- Bedanken für detaillierte Feedback
--- ---
# 70 Things I Regret as a Developer (Auswahl) # Code-Review-Checkliste
- Nicht früher Tests geschrieben zu haben | Bereich | Was prüfen |
- Zu viel Zeit mit "perfektem" Setup statt Code |---------|------------|
- Nicht öfter `git commit` | **Correctness** | macht's, was es soll? Edge-cases? |
- Nicht früher in Pull Requests reviewt | **Readability** | Namen, Struktur, Kommentare? |
- Keine Doku gepflegt | **Tests** | abgedeckt? Sinnvolle Assertions? |
- Eigene Lösungen für Standard-Probleme | **Security** | Input-Validation, Secrets, Auth |
- Frameworks gehyped, Grundlagen vernachlässigt | **Performance** | N+1, Sync-IO, Memory-Leak |
- "Funktioniert bei mir" akzeptiert | **A11y** (Frontend) | Semantik, Kontrast, Keyboard |
| **Doku** | README aktualisiert? CHANGELOG? |
→ Lehre: **Disziplin > Talent.**
--- ---
# Weiterlesen <!-- _class: lead -->
- https://12factor.net/ # Doku lesen lernen
- https://semver.org/
- https://udacity.github.io/git-styleguide/
- https://www.conventionalcommits.org/
- https://refactoring.guru/
- https://martinfowler.com/
- MDN Web Docs · web.dev · web.dev/learn
--- ---
<!-- _class: invert --> # Wo finde ich was?
<!-- _backgroundColor: #000 -->
# Viel Erfolg mit den Projekten! | Ziel | Quelle |
|------|--------|
| Web-APIs (HTML, CSS, JS) | **MDN** — developer.mozilla.org |
| Node-APIs | **nodejs.org/docs** |
| npm-Package | `npmjs.com/package/<name>` + GitHub README |
| Sprach-Standards (ES) | **tc39.es** |
| Browser-Kompatibilität | **caniuse.com** |
| Fehler-Suche | Stack Overflow + GitHub Issues |
Fragen? Probleme? → Telegram, Mail, Sprechstunde. **Faustregel:** offizielle Doku zuerst, dann Stack Overflow.
---
# Stack Overflow gut nutzen
**Bevor du fragst:**
- Existiert die Frage schon? (Suche oben)
- Gibt es ein Minimal-Reproduction-Beispiel?
- Welche Versionen (Node, OS, Library)?
- Was hast du schon versucht?
**Bei der Frage:**
- Konkreter Titel (nicht "JavaScript Hilfe")
- Code-Block für Code
- Was war erwartet, was passiert
- Error-Message als Text (nicht Screenshot)
---
# AI-Assistenten (ChatGPT, Claude, Copilot)
**Pro:**
- Schneller Antworten zu allgemeinen Fragen
- Boilerplate-Code in Sekunden
- Erklärung fremden Codes
**Contra:**
- Halluziniert APIs, die nicht existieren
- Verstärkt Anti-Patterns aus dem Training
- Lernkurve geht zurück, wenn nur kopiert wird
**Faustregel:** als Sparring-Partner, nicht als Wahrheitsquelle. **Verstehen, nicht kopieren.**
---
<!-- _class: aufgabe -->
# Selbstlernen — Projekt-Hygiene-Check
Geht euer Projekt durch:
1. **README:** hat alle 6 Sektionen?
2. **.gitignore:** komplett, keine Secrets im Repo?
3. **Conventional Commits:** letzte 10 Commits durchgehen
4. **package.json:** `version`, `description`, `scripts`?
5. **Tests:** Coverage > 70 %?
6. **Dockerfile + compose.yml:** funktionieren?
7. **Environment:** `.env.example` vorhanden, `.env` gitignored?
**Bonus:** GitHub-Actions-Workflow für Tests + Build.
---
# Abschluss
**Über 8 Termine gelernt:**
1. Web Engineering — Foundation
2. CSS Extended — Layout + Animation
3. Node.js Basics — Runtime + REST
4. Node.js Advanced — async + DB + Auth
5. Testing — Unit + Integration + E2E
6. TypeScript — Typen + Schemas
7. Docker — Container + compose
8. Best Practices — Git + README + SOLID + 12-Factor
**Nächste Schritte:** eigenes Projekt für die Prüfungsleistung. Code-Abgabe + Präsentation.