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
+474 -1434
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>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
section.invert h2 {
color: #f48fb1;
}
pre {
background: #0f0f23;
color: #f48fb1;
border-radius: 8px;
border-left: 3px solid #d63384;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a1a2e;
color: #f48fb1;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
section.invert h2 { color: #f48fb1; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 2 — HTML & CSS (Frameworks)
# Kapitel 2 — CSS Extended
## Box-Model · Flexbox · Grid · Responsive · Animations
---
# Ressourcen zum Selbstlernen
# Recap Kap 1
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
| Bereich | Was wir kennen |
|---------|----------------|
| Anatomie | `selector { property: value }` |
| Selektoren | element, `.class`, `#id`, `:hover` |
| 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
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.)
`box-sizing: border-box` global setzen.
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# CSS: Extended
---
# CSS Selectors – Attribut-Selektoren
# Box-Model in Code
```css
/* presence */
[disabled] { opacity: 0.5; }
* { box-sizing: border-box; }
/* exact value */
[type="email"] { border-color: blue; }
.card {
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 */
[class~="btn"] { cursor: pointer; }
Inspector im DevTools zeigt das Box-Model live an.
/* starts with */
[href^="https"] { color: green; }
---
/* ends with */
[src$=".png"] { border: 1px solid #ccc; }
<!-- _class: lead -->
/* contains substring */
[class*="icon"] { padding-left: 20px; }
# Flexbox
## 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
/* Child (direkt) */
div > p { margin: 0; }
.item {
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) */
div p { margin: 0; }
**Shorthand:** `flex: <grow> <shrink> <basis>`.
/* Next Sibling */
h1 + p { font-size: 1.2em; }
---
/* Subsequent Sibling */
h1 ~ p { color: #666; }
# Typische Flex-Patterns
/* Selector List */
h1, h2, h3 { font-weight: bold; }
```css
/* 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
/* Form States */
: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; }
```
# Grid
## 2D-Layout (Reihe UND Spalte)
---
# Shorthand Properties
# Grid — Basics
```css
/* margin: top right bottom left */
margin: 10px 20px 10px 20px;
/* centering */
margin: 0 auto;
/* 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;
.container {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* 3 Spalten, Verhältnis 1:2:1 */
grid-template-rows: auto 1fr auto; /* Header, Body, Footer */
gap: 1rem;
}
```
`fr` = Fraction Unit — verbleibender Raum aufgeteilt.
---
# Flexbox Shorthands
# Grid — Areas (named layout)
```css
/* flex: grow shrink basis */
flex: 1 0 auto;
.layout {
display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 200px 1fr;
}
/* place-items: align-items justify-items */
place-items: center stretch;
/* place-content: align-content justify-content */
place-content: space-between center;
/* gap: row-gap column-gap */
gap: 20px 40px;
.layout header { grid-area: header; }
.layout nav { grid-area: nav; }
.layout main { grid-area: main; }
.layout footer { grid-area: footer; }
```
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
:root {
--primary-color: #d63384;
--spacing: 20px;
--radius: 8px;
--primary: hotpink;
--gap: 1rem;
--radius: 0.75rem;
}
.button {
background: var(--primary-color);
padding: var(--spacing);
button {
background: var(--primary);
padding: var(--gap);
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
/* calc */
width: calc(100% - 40px);
/* min/max/clamp */
width: min(100%, 600px);
font-size: clamp(14px, 2vw, 20px);
/* 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;
.box {
width: calc(100% - 2rem); /* Mathe */
font-size: clamp(1rem, 2vw, 2rem); /* min, ideal, max */
background: linear-gradient(45deg, hotpink, deeppink);
color: oklch(0.7 0.2 30); /* moderner Farbraum */
filter: blur(2px) brightness(1.2); /* Bildeffekte */
}
```
---
# CSS Frameworks
# CSS-Frameworks
| Framework | Stil |
|-----------|------|
| **Bootstrap** | Komponenten + Grid, opinionated |
| **Tailwind CSS** | Utility-First, atomar |
| **Bulma** | klassisch, ohne JS |
| **shadcn/ui** | kopierbare Komponenten (Tailwind-basiert) |
| **Flowbite** | Tailwind-Komponentenbib. |
---
# Tailwind CSS – Beispiel
| **Tailwind CSS** | Utility-Klassen direkt im HTML |
| **Bootstrap** | Komponenten-Bibliothek (klassisch) |
| **Pico** | klassen-frei, semantisches HTML reicht |
| **Open Props** | CSS-Variablen-Set |
```html
<button class="bg-blue-600 hover:bg-blue-700
text-white font-semibold
px-4 py-2 rounded-lg
transition shadow-sm">
Klick mich
<!-- Tailwind: alle Styles als Klassen -->
<button class="bg-pink-500 hover:bg-pink-600 px-4 py-2 rounded-md">
Klick
</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.
+291 -214
View File
@@ -8,277 +8,354 @@ footer: "Michael Czechowski – SoSe 2026"
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
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);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 4 — Node.js: Scripting, Running and Building
# Kapitel 3 — Node.js Basics
## JavaScript außerhalb des Browsers
---
# Ressourcen zum Selbstlernen
# Was ist Node.js?
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
**Ryan Dahl, 2009.** JavaScript-Runtime auf V8 (Chrome's JS-Engine).
Vorher: JS war Browser-only.
Mit Node: JS auch als Server, CLI-Tools, Build-Skripte.
**Heute:** Node ist Standard-Backend für viele Web-Apps. Netflix, LinkedIn, NASA, Walmart.
---
# Inhalt
# Warum Node?
1. Node.js: was und warum
2. `node --check`, `node -e` (One-Liner)
3. Module: `require` / `import`, `__dirname` / `__filename`
4. HTTP-Server (built-in)
5. `fs` — Dateien lesen / schreiben
6. `package.json` — Aufbau und Lifecycle
7. npm Scripts
8. Module exportieren
| Vorteil | Was bedeutet das |
|---------|------------------|
| **JS überall** | gleiche Sprache Frontend + Backend |
| **NPM** | größtes Package-Ökosystem (~3 Mio Packages) |
| **Non-blocking I/O** | viele Verbindungen gleichzeitig |
| **Schnell genug** | V8 optimiert JS aggressiv |
| **Cross-platform** | Linux, macOS, Windows |
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# Node Versions-Manager
# 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)
- Server-side JavaScript
- npm Package Manager
- 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')"
# Pro Projekt
echo "24" > .nvmrc
fnm use # liest .nvmrc
```
**Aktuell:** Node 24 LTS.
---
# node -e Beispiele
# Projekt initialisieren
```bash
# String
node -e "console.log('Hello ' + process.argv[2])" Welt
```sh
mkdir mein-projekt && cd mein-projekt
npm init -y # erzeugt package.json
# File reading (ESM via --input-type)
node --input-type=module -e \
"import { readFileSync } from 'node:fs'; \
console.log(readFileSync('data.json', 'utf8'))"
# Abhängigkeiten installieren
npm install express # → dependencies
npm install -D vitest # → devDependencies (nur dev)
# JSON parsen
node -e "console.log(JSON.parse(process.argv[1]))" '{"a":1}'
# Skripte ausführen
npm run start
npm test
```
---
# 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>
`-y` = alle Defaults akzeptieren.
---
# Pfad-Auflösung in ESM
```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
# package.json
```json
{
"name": "mein-projekt",
"version": "1.0.0",
"main": "index.js",
"version": "0.1.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js",
"test": "node --test"
"dev": "node --watch src/server.js",
"start": "node src/server.js",
"test": "vitest"
},
"dependencies": {
"express": "^4.18.0"
"express": "^5.0.0"
},
"devDependencies": {
"nodemon": "^3.0.0"
"vitest": "^2.0.0"
}
}
```
`"type": "module"` aktiviert ESM.
---
# npm Scripts
# ESM — modern
```bash
# npm run <script>
npm run start
npm run dev
```javascript
// utils.js
export function greet(name) {
return `Hallo ${name}`;
}
# Mit Argumenten
npm run build -- --production
export const VERSION = "1.0";
# Lifecycle scripts
npm install → postinstall
npm start → start (direkt)
// main.js
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);
```
---
# Node.js Module – exports (ESM)
# REST mit Express
```javascript
// utils.js — named exports
export const add = (a, b) => a + b;
export const greet = (name) => `Hallo ${name}!`;
const users = [{ id: 1, name: 'Ada' }];
// utils.js — default export
export default {
add,
greet
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
// nutzen
import { add, greet } from "./utils.js"; // named
import utils from "./utils.js"; // default
import utils, { add } from "./utils.js"; // beides
```
**Niemals** API-Keys ins Repo committen.
<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.
+278 -194
View File
@@ -8,289 +8,373 @@ footer: "Michael Czechowski – SoSe 2026"
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
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);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 5 — Express API, CRUD und Middlewares
# Kapitel 4 — Node.js Advanced
## async · Files · Database · Auth · Errors
---
# Ressourcen zum Selbstlernen
<!-- _class: lead -->
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
# Asynchron + Promises
---
# 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
**Synchron** (blockierend):
```javascript
const data = readFileSync('huge.txt'); // wartet
console.log('weiter'); // erst danach
```
**Asynchron** (non-blocking):
```javascript
const data = await readFile('huge.txt'); // wartet ohne blockieren
console.log('weiter'); // erst danach
```
Node's Stärke: **non-blocking I/O** für viele parallele Operationen.
---
<!-- _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
# Promise — Anatomie
```javascript
process.on("SIGINT", () => {
console.log("Received SIGINT. Closing server...");
server.close(() => {
console.log("Server closed.");
process.exit(0);
});
});
const promise = fetch('https://api.example.com');
// Signale
process.exit(0); // Erfolgreich
process.exit(1); // Fehler
// then-style (alt)
promise
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
// async/await (modern)
try {
const res = await fetch('https://api.example.com');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
```
---
# node.js Module System
# Sequenziell vs. Parallel
```javascript
// ES Modules (modern, "type": "module")
import express from "express";
import { port } from "./src/config/constants.js";
import { readFileSync } from "node:fs";
// SEQUENZIELL — langsam (warten + warten + warten)
const a = await fetch('/api/a');
const b = await fetch('/api/b');
const c = await fetch('/api/c');
// Gesamtzeit: a + b + c
// PARALLEL — schnell (alle gleichzeitig starten)
const [a, b, c] = await Promise.all([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c'),
]);
// Gesamtzeit: max(a, b, c)
```
---
# Promise-Methoden
| Methode | Was |
|---------|-----|
| `Promise.all([…])` | alle erfolgreich ODER ein Fehler |
| `Promise.allSettled([…])` | alle, Resultat = { status, value/reason } |
| `Promise.race([…])` | erstes Resultat gewinnt (auch Fehler) |
| `Promise.any([…])` | erstes erfolgreiches, ignore Fehler |
```javascript
// CommonJS (legacy)
const express = require("express");
const fs = require("fs");
const fastest = await Promise.race([
fetch('/api'),
new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000))
]);
```
→ Neue Projekte: ESM. Built-ins immer mit `node:`-Präfix.
---
# HTTP Request mit node-fetch
<!-- _class: lead -->
# Filesystem
---
# fs/promises — Files lesen + schreiben
```javascript
// Node.js 18+ hat fetch eingebaut
const res = await fetch('https://api.example.com/data');
const data = await res.json();
import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
// Ältere Versionen: node-fetch
import fetch from 'node-fetch';
// Lesen
const text = await readFile('input.txt', 'utf-8');
const buf = await readFile('image.png'); // Buffer
// Schreiben
await writeFile('output.txt', 'Hallo Welt');
await writeFile('data.json', JSON.stringify({ x: 1 }, null, 2));
// Verzeichnis lesen
const files = await readdir('./uploads');
// Verzeichnis anlegen
await mkdir('./tmp', { recursive: true });
```
---
# Web Server, Builder, Bundler
```bash
# Boilerplates erstellen
npm create vite@latest my-app -- --template react
npm create svelte@latest
npm create astro@latest
# nuxt
npm install nuxt
npm run dev
```
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/js-frameworks-overview.png)
---
# Express – Grundlagen
# Streams für große Dateien
```javascript
import express from 'express';
const app = express();
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
app.listen(port, () => {
console.log(`Server läuft auf Port ${port}`);
});
// 10 GB Logfile ohne RAM-Explosion verarbeiten
await pipeline(
createReadStream('huge.log'),
// ... transform-stream ...
createWriteStream('output.log')
);
```
Streams = Daten **stückweise** verarbeiten, nicht alles in RAM.
---
# Express – Middleware
<!-- _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
app.use(express.json());
import Database from 'better-sqlite3';
const db = new Database('app.db');
app.use('/api', (req, res, next) => {
console.log('Time:', Date.now());
next();
});
db.prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`).run();
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});
// Insert
const insert = db.prepare('INSERT INTO users (name) VALUES (?)');
insert.run('Ada');
// Query
const users = db.prepare('SELECT * FROM users').all();
const ada = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
```
---
# Express – CRUD Routes
# 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('/api/:key', async (req, res) => {
const context = cache.get(req.params.key);
res.json({ context });
app.get('/users', (req, res) => {
const users = db.prepare('SELECT * FROM users').all();
res.json(users);
});
app.post('/api/:key', async (req, res) => {
const payload = req.body;
res.json({ created: cache.set(req.params.key, payload) });
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.put('/api/:key', async (req, res) => {
const payload = req.body;
res.json({ updated: cache.update(req.params.key, payload) });
});
app.delete('/api/:key', async (req, res) => {
cache.remove(req.params.key);
res.json({ deleted: req.params.key });
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 });
});
```
---
# Express – server.listen
<!-- _class: lead -->
# Authentifizierung
---
# Auth-Strategien
| Strategie | Wann |
|-----------|------|
| **Session-Cookie** | klassische Web-App, Server-Side Render |
| **JWT** (JSON Web Token) | API + SPA, stateless |
| **OAuth** | „Mit Google einloggen", Drittpartei-Login |
| **Magic Link** | Email statt Passwort, modern |
| **Passkeys** | passwordless, FIDO2, Zukunft |
Für DHBW-Projekt: **Session-Cookie** (express-session) ODER **JWT**.
---
# Passwörter — *niemals plaintext*
```javascript
const server = app.listen(port, () => {
console.log(`Server läuft auf Port ${port}`);
});
import bcrypt from 'bcrypt';
server.on('error', (err) => {
console.error('Server error:', err);
});
// Registrieren
const hashed = await bcrypt.hash('mein-passwort', 12);
db.prepare('INSERT INTO users (email, password) VALUES (?, ?)').run(email, hashed);
server.close(() => {
console.log('Server geschlossen');
});
// 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.
---
# WebSockets
# JWT — kurzer Überblick
```javascript
import { WebSocketServer } from 'ws';
import jwt from 'jsonwebtoken';
const wss = new WebSocketServer({ port: 8080 });
// 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 });
wss.on('connection', (ws) => {
console.log('Client verbunden');
// Verifizieren (Middleware)
const token = req.cookies.token;
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
```
ws.on('message', (data) => {
console.log('Empfangen:', data.toString());
ws.send('Pong');
});
});
`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);
}
```
---
<!-- _header: '' -->
<!-- _footer: '' -->
# Globaler Express-Error-Handler
![bg fit](./assets/demos/ts-js-compilation.png)
```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.
---
<!-- _header: '' -->
<!-- _footer: '' -->
<!-- _class: aufgabe -->
![bg fit](./assets/demos/docker-orchestration.png)
# 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.
---
<!-- _header: '' -->
<!-- _footer: '' -->
![bg fit](./assets/demos/test-automation-trophy.png)
# 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.
+294 -285
View File
@@ -9,366 +9,375 @@ title: Testing
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
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);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 7 — Testing
# Kapitel 5 — Testing
## Unit · Integration · E2E · TDD
---
# Ressourcen zum Selbstlernen
# Warum testen?
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
| Vorteil | Was bedeutet das |
|---------|------------------|
| **Confidence** | Refactor ohne Angst |
| **Regression-Schutz** | alter Bug taucht nicht wieder auf |
| **Living Documentation** | Tests zeigen, was Code soll |
| **CI/CD-Voraussetzung** | automatisch vor jedem Deploy |
**„Untested Code is broken by design."**
---
<!-- _class: invert -->
<!-- _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
# Test-Pyramide
```
▲ manual / exploratory
▲ ▲ E2E (UI + API)
▲▲▲▲ Integration (class + real deps)
▲▲▲▲▲▲ Unit (class + mocked deps)
▲▲▲▲▲▲▲▲ Static analysis (compiler, linter)
/\
/E2E\ wenige (langsam, fragil)
/-----\
/ Integ \ einige (mittel)
/---------\
/ Unit \ viele (schnell, isoliert)
/-------------\
```
- Oben: wenige, langsam, teuer, brüchig
- Unten: viele, schnell, billig, stabil
**Faustregel:** 70 % Unit, 20 % Integration, 10 % E2E.
---
# 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
// cache.unit.test.js
import { describe, it, expect } from "vitest";
import { Cache } from "./cache.js";
// utils.test.js
import { describe, it, expect } from 'vitest';
import { greet } from './utils.js';
describe("Cache", () => {
it("creates and retrieves data with custom key", () => {
const cache = new Cache();
const data = { user: "wolfgang", city: "stuttgart" };
describe('greet', () => {
it('returns greeting with name', () => {
expect(greet('Ada')).toBe('Hallo Ada');
});
it('handles empty string', () => {
expect(greet('')).toBe('Hallo ');
});
});
```
const key = cache.create(data, "custom-key");
```sh
npx vitest
npx vitest --coverage
```
expect(key).toBe("custom-key");
expect(cache.get("custom-key")).toEqual(data);
---
# Test-Struktur (AAA)
```javascript
it('adds two numbers', () => {
// ARRANGE — Setup
const a = 2;
const b = 3;
// 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)
.post('/users')
.send({ name: 'Ada' });
expect(res.status).toBe(201);
expect(res.body.name).toBe('Ada');
});
});
```
---
# Integration Tests – Eigenschaften
- **Reale Komponenten** – echte Cache + Express-Instanzen
- **Supertest-Magie** – simuliert HTTP ohne Server-Startup
- **In-Process** – alles im selben Node-Prozess
- **Kein Netzwerk** – keine TCP-Connections
→ teste Zusammenspiel mehrerer Module ohne Deployment.
---
# Integration Test – Beispiel (supertest)
# Test-Datenbank
```javascript
// app.integration.test.js
import request from "supertest";
import { app, cache } from "./app.js";
import { beforeAll, afterEach, afterAll } from 'vitest';
import Database from 'better-sqlite3';
test("full CRUD lifecycle with real cache", async () => {
const res = await request(app)
.post("/api/entries")
.send({ name: "integration-test", status: "active" });
let db;
expect(res.status).toBe(201);
const { key } = res.body;
expect(cache.keys()).toContain(key);
beforeAll(() => {
db = new Database(':memory:'); // in-RAM, super schnell
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
});
afterEach(() => {
db.exec('DELETE FROM users'); // sauberer Start
});
afterAll(() => db.close());
```
---
# End-to-End Tests (API)
- **Echter Server** – HTTP auf TCP-Port
- **Echtes Netzwerk** – `fetch` über localhost
- **Echte Umgebung** – CORS, Middleware-Stack
- **User-Perspektive** – exakt was Clients sehen
`:memory:`-DB = jeder Test isoliert, blitzschnell.
---
# E2E API Test – Beispiel
# Mocking
```javascript
// index.e2e.test.js
import { createServer } from "node:http";
import { app } from "./app.js";
import { vi, it, expect } from 'vitest';
let server;
const TEST_PORT = 8081;
const baseUrl = `http://localhost:${TEST_PORT}`;
beforeAll(async () => {
server = createServer(app);
await new Promise((r) => server.listen(TEST_PORT, r));
});
afterAll(() => server.close());
test("complete user journey", async () => {
const res = await fetch(`${baseUrl}/api`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "e2e", timestamp: Date.now() }),
it('calls fetch with right URL', async () => {
const mockFetch = vi.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ name: 'Ada' })
});
expect(res.status).toBe(201);
await getUserData(1);
expect(mockFetch).toHaveBeenCalledWith('https://api/users/1');
});
```
---
# 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**
**Mock sparsam** — übermockt-Tests testen Mocks, nicht Code.
---
# Cypress – Minimalbeispiel
<!-- _class: lead -->
# E2E — End-to-End
---
# Playwright — moderne E2E
```sh
npm init playwright@latest
```
```javascript
// cypress/e2e/counter.cy.js
describe("Counter App", () => {
it("increments on click", () => {
cy.visit("http://localhost:3000");
cy.contains("button", "Clicked 0 times").click();
cy.contains("button", "Clicked 1 times");
});
import { test, expect } from '@playwright/test';
test('login flow', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.getByLabel('Email').fill('ada@example.com');
await page.getByLabel('Password').fill('secret123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Hallo Ada')).toBeVisible();
});
```
```bash
npx cypress open # GUI
npx cypress run # headless / CI
```
Browser tatsächlich öffnen + interagieren. **Realer User-Flow.**
---
# Playwright – Minimalbeispiel
# Playwright-Locators
```javascript
// tests/counter.spec.js
import { test, expect } from "@playwright/test";
// Bevorzugt: rollenbasiert (A11y-konform!)
page.getByRole('button', { name: 'Submit' });
page.getByLabel('Username');
page.getByText('Welcome');
test("counter increments", async ({ page }) => {
await page.goto("http://localhost:3000");
await page.getByRole("button").click();
await expect(page.getByRole("button")).toContainText("Clicked 1 times");
});
// Fallback: CSS-Selektor
page.locator('.submit-btn');
page.locator('#username');
```
```bash
npx playwright test
npx playwright test --ui
```
**Rollenbasierte Locators** finden Bugs, die A11y betreffen.
---
# Coverage Reports
# E2E vs. Unit — Trade-offs
```bash
npx vitest run --coverage
```
| | Unit | E2E |
|--|------|-----|
| Geschwindigkeit | ms | s |
| Isolation | hoch | gering |
| Realismus | gering | hoch |
| Wartung | wenig | viel |
| Wann | jede Funktion | kritische Flows |
```
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`
E2E nicht für jede Detail-Logik — für **happy path + Login + Bezahlung**.
---
# Best Practices
<!-- _class: lead -->
- **AAA**: Arrange · Act · Assert
- Tests sind **Dokumentation** – beschreibend benennen
- Ein Verhalten pro Test
- Keine Logik im Test (keine `if`/`for`)
- **Test Doubles**: stub, mock, spy, fake
- CI: Tests bei jedem Push
# Coverage + CI
---
# Live Coding
# Coverage-Report
Repo: `node-cache-api`
```bash
git clone https://github.com/nextlevelshit/node-cache-api
cd node-cache-api
npm install
npm test # unit + integration
npm run test:e2e # e2e
```sh
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.
+271 -342
View File
@@ -9,423 +9,352 @@ title: TypeScript
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
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);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 8 — TypeScript
# Kapitel 6 — TypeScript
## Typen über JavaScript
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# Was ist TypeScript?
# 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
2. `tsc` + `ts-node`
3. `tsconfig.json`
4. Type Primitives
5. Union Types, Type Alias, Interface
6. Enum, Generics, Type Casting
7. Compilation: types stripped
8. Decorators + reflect-metadata
| Vorteil | Was bedeutet das |
|---------|------------------|
| **Fehler vor Laufzeit** | Compiler fängt typos, falsche Aufrufe |
| **Bessere IDE** | Autocompletion, Refactoring, Jump-to-Definition |
| **Selbst-Doku** | Typ-Signaturen sind ausführbare Doku |
| **Refactoring sicher** | Compiler findet alle Auswirkungen |
**Nachteil:** Build-Step + Lernkurve.
---
# TypeScript – Was?
# Setup
- **Superset von JavaScript** (Microsoft, 2012)
- Jede `.js` lässt sich zu `.ts` umbenennen
- **Type Errors zur Compile-/Transpile-Zeit**
- Neuere ECMAScript-Features verfügbar
- **Im Browser:** läuft nicht direkt – wird zu JS kompiliert
---
# Setup: tsc + ts-node
```bash
npm i -D typescript ts-node @types/node
npx tsc --init # erzeugt tsconfig.json
```sh
npm install -D typescript @types/node tsx
npx tsc --init # tsconfig.json erzeugen
```
**Run:**
```bash
npx ts-node index.ts # statt: node index.js
```
`tsx` = TypeScript direkt ausführen ohne Compile-Step (Dev).
**Build:**
```bash
npx tsc # kompiliert nach ./dist
node dist/index.js
```json
// package.json
{
"scripts": {
"dev": "tsx --watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
}
}
```
---
# tsconfig.json
# tsconfig.json — Wichtigste
```json
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
"noImplicitAny": true,
"strictNullChecks": true,
"outDir": "dist",
"rootDir": "src"
}
}
```
**`strict: true`** = alle strengen Checks an. **Pflicht** für neue Projekte.
---
# Type Primitives
# Basis-Typen
```typescript
let loading: boolean = false;
let count: number = 42;
let pi: number = 3.14;
let name: string = "DHBW ist fetzig!";
let empty: null = null;
let missing: undefined = undefined;
let name: string = "Ada";
let age: number = 56;
let admin: boolean = true;
let nothing: null = null;
let unset: undefined = undefined;
function log(msg: string): void {
console.log(msg);
let hobbies: string[] = ["math", "chess"];
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.
---
# 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
interface User {
name: string;
age?: number; // optional
readonly id: number; // schreibgeschützt
}
const ada: User = { id: 1, name: "Ada", age: 56 };
// Type-Alias (Interface mit anderem Namen, mehr Flexibilität)
type Status = "pending" | "active" | "deleted";
```
---
# Function-Typen
```typescript
// Argument + Return
function add(a: number, b: number): number {
return a + b;
}
// 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();
}
```
---
# Union + Intersection
```typescript
// Union — "ODER"
type Id = string | number;
function lookup(id: string | number) { /* ... */ }
// Discriminated Union
type Shape =
| { 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[] };
```
---
# Generics — typ-flexible Funktionen
```typescript
// T ist ein Platzhalter
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
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; // optional
readonly createdAt: Date; // schreibgeschützt
greet(msg: string): void;
email: string;
createdAt: string;
}
const u: User = {
id: 1,
name: "Lisa",
createdAt: new Date(),
greet(msg) { console.log(msg); }
};
// Express-Route typisiert
import type { Request, Response } from 'express';
app.post('/users', (req: Request<{}, {}, CreateUserBody>, res: Response<UserResponse>) => {
const { name, email } = req.body; // typsicher
// ...
});
```
---
# Interface vs Type
# Zod — Runtime-Validation
**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 prüft zur Compile-Time. **Was kommt aus dem Netz?**
```typescript
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
import { z } from 'zod';
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";
}
}
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
walk(Direction.Up); // "bow"
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
});
```
---
# Generics
# `any` vs `unknown` vs `never`
```typescript
class GenericContainer<T> {
private readonly value: T;
let a: any = "test";
a.toFixed(); // OK — TS prüft NICHTS
constructor(value: T) {
this.value = value;
}
let u: unknown = "test";
u.toFixed(); // FEHLER — erst type-narrow
if (typeof u === 'number') u.toFixed(); // OK
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");
function fail(msg: string): never {
throw new Error(msg); // kommt nie zurück
}
```
**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.
**`unknown` statt `any`** — sicherer Default für fremde Daten.
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# Best Practices
# Decorators
## Meta-Programmierung mit TypeScript
- **`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
---
# TypeScript: Decorators
<!-- _class: aufgabe -->
Vier Typen:
# Selbstlernen — eure API zu TS migrieren
- **Class Decorators** – auf ganze Klasse
- **Property Decorators** – auf Felder
- **Method Decorators** – auf Methoden
- **Parameter Decorators** – auf Methoden-Parameter
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
Genutzt von: Angular, NestJS, TypeORM, class-validator.
**Bonus:** Drizzle-ORM verwenden (TypeScript-first).
---
# Decorators aktivieren
# Zusammenfassung
**`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;
}
}
new Calculator().add(2, 3); // logs: → add([2,3])
```
---
# Reflect: Meta-Daten lesen
```typescript
import "reflect-metadata";
Reflect.defineMetadata("role", "admin", target);
Reflect.hasMetadata("role", target); // true
Reflect.getMetadata("role", target); // "admin"
```
Frameworks (NestJS, TypeORM) nutzen das, um zur **Runtime** Type-Informationen zu rekonstruieren – z. B. für DI, ORM-Mapping, Validierung.
---
# Anwendung: NestJS-Beispiel
```typescript
@Controller("users")
export class UsersController {
constructor(private users: UsersService) {}
@Get(":id")
findOne(@Param("id") id: string) {
return this.users.find(+id);
}
}
```
`@Controller`, `@Get`, `@Param` sind Decorators – Metadaten ⇒ Routing.
**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>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
section.invert h2 {
color: #f48fb1;
}
pre {
background: #0f0f23;
color: #f48fb1;
border-radius: 8px;
border-left: 3px solid #d63384;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a1a2e;
color: #f48fb1;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 9 — Docker, Proxies and DBs
# Kapitel 7 — Docker
## Container für reproducible Setups
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# „Auf meinem Rechner läuft's"
# 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
2. Container vs VM
3. `Dockerfile`
4. `compose.yml`
5. Docker CLI / Compose Befehle
6. Live Demo: `dhbw-docker`
| | Container | Virtual Machine |
|--|-----------|-----------------|
| Startzeit | ms | min |
| Größe | MB | GB |
| Kernel | shared (Host-Linux) | eigener |
| Isolation | Prozess-Level | Hardware-Level |
| Overhead | minimal | hoch |
Container = leichtgewichtige Prozess-Isolation.
---
# Architektur
# Docker — die Idee
```
Browser (localhost:10007)
│
▼
┌──────────────────┐
│ Reverse Proxy │ (nginx / traefik)
└─────────┬────────┘
┌────┴────┐
▼ ▼
┌─────────┐ ┌────────┐
│ Web │ │ API │
│ App │ │ (Node) │
└────┬────┘ └────┬───┘
└─────┬──────┘
▼
┌────────┐
│ DB │ (Postgres)
└────────┘
- **Image** = Schnappschuss (App + Files + OS-Layer)
- **Container** = laufende Instanz eines Images
- **Dockerfile** = Anleitung zum Image-Bauen
- **Registry** = Image-Speicher (Docker Hub, GHCR)
```sh
docker pull node:24
docker run node:24 node --version
```
---
# Container vs VM
| | VM | Container |
|---|----|-----------|
| Kernel | eigener | Host-Kernel |
| Boot | Minuten | Sekunden |
| Größe | GB | MB |
| Isolation | stark | namespaces, cgroups |
| Image | komplett | Layer (diff) |
→ **Docker** = Container-Engine + Image-Format + Registry.
---
# Dockerfile – Express API
# Dockerfile — Hallo Welt
```dockerfile
FROM node:lts-slim AS builder
FROM node:24-alpine
WORKDIR /app
COPY ./package*.json ./
RUN npm ci --no-audit --no-fund --no-progress --loglevel=error
COPY package*.json ./
RUN npm ci
COPY ./ ./
COPY . .
ENV NODE_ENV="production"
EXPOSE 8080
CMD ["npm", "start"]
EXPOSE 3000
CMD ["node", "src/server.js"]
```
**`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
```dockerfile
FROM node:lts-slim AS builder
# Stage 1: Build mit allem
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
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
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 8080
CMD ["node", "dist/index.js"]
EXPOSE 3000
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
Verhindert dass Müll ins Image kommt:
```
node_modules
dist
.git
.env
*.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
name: dhbw-docker-app
services:
api:
build: ./api
container_name: dhbw-api
environment:
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_HOST: db
DATABASE_PORT: 5432
app:
build: .
ports:
- "10000:8080"
networks: [net, data]
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on:
db:
condition: service_healthy
```
- db
---
# compose.yml – Datenbank + Proxy
```yaml
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- dbdata:/var/lib/postgresql/data
networks: [data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$DATABASE_USERNAME"]
interval: 5s
retries: 5
proxy:
image: nginx:alpine
ports: ["10007:80"]
volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
networks: [net]
depends_on: [api]
- db-data:/var/lib/postgresql/data
volumes:
dbdata:
networks:
net:
data:
db-data:
```
---
# Docker Compose – Befehle
# compose-Commands
```bash
docker compose -p "dhbw-docker-app" \
-f compose.yml \
--env-file .env \
up \
--build \
--remove-orphans \
--force-recreate
```
```sh
docker compose up # alles starten (Foreground)
docker compose up -d # detached (background)
docker compose down # alles stoppen + entfernen
docker compose down -v # ... und Volumes löschen
| Flag | Bedeutung |
|------|-----------|
| `-p` | Projektname |
| `-f` | mehrere Compose-Files möglich |
| `--env-file` | `.env` einlesen |
| `--build` | Images neu bauen |
| `--remove-orphans` | verwaiste Container weg |
| `--force-recreate` | Container neu starten |
---
# Compose – Alltag
```bash
docker compose up -d # detached starten
docker compose ps # Status
docker compose logs -f api # Logs streamen
docker compose exec api sh # Shell im Container
docker compose down -v # Stop + Volumes löschen
docker compose restart api # Neustart einzelner Service
docker compose logs -f app # Logs streamen
docker compose exec app sh # Shell im laufenden Container
docker compose ps # Status sehen
```
---
# Health Checks
# Networking in compose
Standard: alle Services im selben `default`-Netzwerk.
```yaml
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
services:
app:
environment:
# `db` ist der Service-Name → DNS-Hostname
DATABASE_URL: postgres://...@db:5432/app
```
`depends_on: { condition: service_healthy }` wartet, bis Health passt.
**Service-Namen werden zu Hostnamen.** Kein localhost zwischen Containern.
---
# Networks: Segmentierung
- **`net`** – öffentlich erreichbar (Proxy — App)
- **`data`** – nur intern (App — DB)
- DB ist **nicht** vom Internet erreichbar
- Service-DNS: `api`, `db`, `proxy` (Container-Name = Hostname)
→ Defense in Depth.
---
# Volumes – Daten persistent
# Volumes — Daten überleben
```yaml
volumes:
dbdata: # benannt
- ./code:/app # bind mount (dev)
- /app/node_modules # anonymous (überschreibt bind)
db-data: # named volume (managed by Docker)
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
```bash
git clone https://github.com/nextlevelshit/dhbw-docker
cd dhbw-docker
cp .env.example .env
docker compose up --build
```yaml
services:
app:
env_file: .env # ganze .env-Datei laden
environment:
NODE_ENV: production # einzeln setzen
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
- Dockerfile · Layer Cache · BuildKit
- Compose · Stack · Swarm · Kubernetes
- Registry · Tag · Digest
- Dev/Prod-Parity (12-Factor)
- Secrets · Configs
- Logging Drivers
- Health Checks · Restart Policies
# CI/CD
---
# 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>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #d63384;
--color-dimmed: #4a4a6a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.4rem;
}
h1 {
color: #a02060;
}
section.invert h1 {
color: #fff;
}
h2 {
color: #1f2937;
}
section.invert h2 {
color: #f48fb1;
}
pre {
background: #0f0f23;
color: #f48fb1;
border-radius: 8px;
border-left: 3px solid #d63384;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a1a2e;
color: #f48fb1;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026** · Sitzung 10 — Wrap-up & Projektsupport
# Kapitel 8 — Best Practices
## Git · README · SOLID · 12-Factor · SemVer
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
<!-- _class: lead -->
# Best Practices
## Wrap-up & Projektsupport
# Git-Hygiene
---
# Inhalt
1. Semantic Versioning
2. Git Commit Messages (Udacity Style)
3. 12 Factor App
4. Make it Work · Make it Right · Make it Fast
5. Präsentations-Tipps
6. Feedback (Sandwich-Methode)
7. Prüfungsleistung Recap
---
# Semantic Versioning (SemVer 2.0.0)
```
MAJOR.MINOR.PATCH
│ │ └─ Bugfix, abwärtskompatibel
│ └────── Neue Funktion, abwärtskompatibel
└──────────── Breaking Change
```
**Beispiele:**
- `1.4.2` → `1.4.3` Bugfix
- `1.4.3` → `1.5.0` neues Feature
- `1.5.0` → `2.0.0` Breaking Change
Pre-Release: `1.0.0-alpha.1`, `1.0.0-rc.2`
→ https://semver.org/
---
# package.json: Versionsranges
```json
{
"dependencies": {
"express": "^4.19.2", // ≥4.19.2 < 5.0.0 (gleiche MAJOR)
"lodash": "~4.17.21", // ≥4.17.21 < 4.18.0 (gleiche MINOR)
"react": "19.0.0" // exakt
}
}
```
`package-lock.json` pinnt die **tatsächlich installierten** Versionen.
---
# Git Commit Messages – Udacity Style
```
<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
```
# Sinnvolle Commits
**Schlecht:**
```
fixed stuff
WIP
update
fix
wip
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
```
feat(auth): add OAuth2 login
fix(api): return 404 instead of 500 on missing user
docs: update README install steps
chore(deps): bump express from 4.18 to 4.19
BREAKING CHANGE: drop Node 16 support
<type>(<scope>): <subject>
[optional body]
[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 |
|---|--------|
| 1 | Codebase – ein Repo, viele Deploys |
| 2 | Dependencies – explizit deklariert (`package.json`) |
| 3 | Config – via Environment-Variablen |
| 4 | Backing Services – als Attached Resources |
| 5 | Build, Release, Run – getrennt |
| 6 | Processes – stateless |
# Build outputs
dist/
build/
.next/
.cache/
# Secrets
.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 |
|---|--------|
| 7 | Port Binding – self-contained, exportiert HTTP |
| 8 | Concurrency – horizontal skalieren via Prozesse |
| 9 | Disposability – schneller Start, Graceful Shutdown |
| 10 | Dev/Prod Parity – möglichst gleiche Umgebung |
| 11 | Logs – als Event-Streams (`stdout`) |
| 12 | Admin Processes – als One-Off |
```sh
# Neuer Feature-Branch
git checkout -b feat/user-profile
# Arbeit, Commits
git add .
git commit -m "feat(profile): add avatar upload"
# 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
// schlecht
const db = "postgres://user:secret@prod-db:5432/app";
// Schlecht — alles in einer Klasse
class UserManager {
validate() { /* ... */ }
saveToDb() { /* ... */ }
sendEmail() { /* ... */ }
generateReport() { /* ... */ }
}
// gut
const db = process.env.DATABASE_URL;
// Besser — getrennte Verantwortlichkeiten
class UserValidator { /* ... */ }
class UserRepository { /* ... */ }
class EmailService { /* ... */ }
class ReportGenerator { /* ... */ }
```
```bash
# .env (NICHT committen!)
DATABASE_URL=postgres://user:secret@db:5432/app
NODE_ENV=production
PORT=8080
---
# D — Dependency Inversion
```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
```
1. Make It Work → MVP, hässlich aber funktional
2. Make It Right → Refactor, Tests, Clean Code
3. Make It Fast → Profiling, Optimierung
| # | Faktor | Was |
|---|--------|-----|
| 1 | Codebase | ein Repo pro App, mehrere Deploys |
| 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.
Don't refactor what isn't working yet.
→ https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
`.env` lokal, **Environment-Variablen** auf Server. Gleicher Code überall.
---
# KISS · DRY · YAGNI · SOLID
# Factor 6 — Stateless Processes
- **KISS** – Keep It Simple, Stupid
- **DRY** – Don't Repeat Yourself
- **YAGNI** – You Aren't Gonna Need It
- **SOLID**:
- **S**ingle Responsibility
- **O**pen/Closed
- **L**iskov Substitution
- **I**nterface Segregation
- **D**ependency Inversion
**Schlecht:** Session-State im RAM.
```javascript
const sessions = new Map(); // pro Server-Instanz!
```
→ Load-Balancer mit 2 Servern: User-Session geht verloren beim 2. Request.
→ 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)
- **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?
# SemVer — semantisches Versioning
---
# Präsentation – Pitfalls
# SemVer — Format
- Am Thema vorbeireden
- Folien vorlesen
- Live-Demo ohne Backup-Video
- "Sieht man hier nicht so gut, aber..."
- Zu viel Text pro Folie
- Keine klare Botschaft pro Slide
```
MAJOR.MINOR.PATCH
1 . 4 . 3
```
→ **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
2. **Kern** — Kritik + konkreter Verbesserungsvorschlag
3. **Abschluss** — Lob / wertschätzender Ausklang
```json
{
"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:**
- Am Thema vorbeireden
- Bei Unklarheiten keine Fragen stellen
- Subjektives Feedback ("Farbe gefällt mir nicht")
→ **Konkret, sachlich, umsetzbar.**
**Faustregel:** `^` (Caret) ist Default — major-version-lock.
---
# Prüfungsleistung – Recap
<!-- _class: lead -->
**Grundanforderungen (75 Punkte):**
| 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
# Code-Reviews
---
# Abgabe + Termine
# Constructive Code-Reviews
- **Code-Upload:** bis 27.07.
- **Präsentation:** 17.07. (in Gruppen, ~10 Min)
- Pitch (~1 Min)
- Agenda
- Idee / Problem
- Learnings & Pitfalls
- "It is what it is" – Doku
**Geben:**
- Konkret: Zeilennummern + Beispiel
- Wertschätzend: gut gemachte Stellen erwähnen
- Fragend statt fordernd: „Wäre `X` hier robuster?"
- Klar zwischen MUST/SHOULD/CONSIDER unterscheiden
**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
- Zu viel Zeit mit "perfektem" Setup statt Code
- Nicht öfter `git commit`
- Nicht früher in Pull Requests reviewt
- Keine Doku gepflegt
- Eigene Lösungen für Standard-Probleme
- Frameworks gehyped, Grundlagen vernachlässigt
- "Funktioniert bei mir" akzeptiert
→ Lehre: **Disziplin > Talent.**
| Bereich | Was prüfen |
|---------|------------|
| **Correctness** | macht's, was es soll? Edge-cases? |
| **Readability** | Namen, Struktur, Kommentare? |
| **Tests** | abgedeckt? Sinnvolle Assertions? |
| **Security** | Input-Validation, Secrets, Auth |
| **Performance** | N+1, Sync-IO, Memory-Leak |
| **A11y** (Frontend) | Semantik, Kontrast, Keyboard |
| **Doku** | README aktualisiert? CHANGELOG? |
---
# Weiterlesen
<!-- _class: lead -->
- https://12factor.net/
- 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
# Doku lesen lernen
---
<!-- _class: invert -->
<!-- _backgroundColor: #000 -->
# Wo finde ich was?
# 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.