Ausführlicher Lernpfad: Codex, Claude Code, Java Enterprise & Frontend-Stacks
Stand: 02.07.2026
Ziel: Du lernst, wie du Codex und Claude Code sinnvoll als Coding-Agenten nutzt, wie du Java-Enterprise-Projekte sauber startest und wie du moderne Frontend-Stacks aufsetzt.
Inhaltsverzeichnis
- Zielbild und Lernstrategie
- Grundsetup für Entwickler
- Codex Lernpfad
- Claude Code Lernpfad
- Codex vs. Claude Code: Wann welches Tool?
- AI-Coding-Workflow für echte Projekte
- Java Enterprise Lernpfad
- Java Enterprise Projekt starten
- Backend-Architektur für Enterprise-Projekte
- Frontend Lernpfad
- Frontend-Stacks erstellen
- Fullstack-Integration: Java Backend + Frontend
- Testing, Security, Observability und DevOps
- 12-Wochen-Roadmap
- Masterprojekt
- Beispielprojekt: Customer Order Portal
- Beispielprojekt: Erweiterter HTML Manager
- Beispielprojekt: Ticket & Approval Workflow
- Beispielprojekt: WordPress Pizza-Restaurant mit Lieferung, SEO, Sicherheit und App
- Prompt-Bibliothek für Codex und Claude Code
- Checklisten
- Quellen und offizielle Doku
1. Zielbild und Lernstrategie
Du willst nicht nur Framework-Namen kennen, sondern Projekte sauber starten können. Der Lernpfad hat deshalb drei Ebenen:
| Ebene | Was du lernst | Ergebnis |
|---|---|---|
| Tool-Ebene | Codex und Claude Code bedienen, sicher prompten, Änderungen prüfen | Du kannst Coding-Agenten kontrolliert im Repo einsetzen. |
| Backend-Ebene | Java, Spring Boot, Jakarta EE, Quarkus/Micronaut, Datenbank, Security, Tests | Du kannst ein Enterprise-Backend produktionsnah starten. |
| Frontend-Ebene | TypeScript, React/Vite, Angular, Next.js, Vue, UI, State, API-Integration | Du kannst moderne Frontends sauber aufsetzen. |
Wichtiges Prinzip: AI-Tools ersetzen nicht deine Architekturentscheidung. Sie beschleunigen Recherche, Boilerplate, Refactoring, Tests und Migrationen. Du bleibst verantwortlich für Fachlogik, Sicherheit, Datenmodell, Architektur und Review.
Erklärung: Wie du diesen Lernpfad praktisch benutzt
Dieser Lernpfad ist nicht als reine Liste von Technologien gedacht. Er ist eher wie ein Bauplan: Du lernst zuerst die Grundwerkzeuge, dann baust du kleine Module, danach verbindest du Backend, Frontend, Datenbank, Sicherheit, Tests und Deployment. So vermeidest du den typischen Fehler, zehn Frameworks gleichzeitig anzufangen, aber kein vollständiges Projekt sauber fertigzustellen.
Die wichtigste Denkweise: Jedes Thema sollte am Ende in einem kleinen lauffähigen Ergebnis landen. Bei Java bedeutet das zum Beispiel nicht nur „Spring Boot lesen“, sondern einen REST-Endpunkt bauen, Daten speichern, Fehler sauber behandeln und Tests schreiben. Bei Frontend bedeutet es nicht nur „React oder Angular installieren“, sondern Formulare, API-Aufrufe, Ladezustände, Fehlerzustände und responsive Layouts umzusetzen. Bei Codex und Claude Code bedeutet es nicht nur „Prompt schreiben“, sondern Aufgaben klein schneiden, Änderungen prüfen, Tests ausführen und den Git-Diff verstehen.
| Frage | Gute Lernentscheidung |
|---|---|
| „Was soll ich zuerst lernen?“ | Erst Git, Terminal, Java, TypeScript, HTTP und Datenbank-Grundlagen. Danach Frameworks. |
| „Soll ich Spring Boot, Quarkus oder Jakarta EE nehmen?“ | Für den Einstieg meistens Spring Boot. Quarkus/Micronaut später für Cloud-native oder spezielle Performance-Ziele. Jakarta EE lernen, um Enterprise-Standards zu verstehen. |
| „React oder Angular?“ | React/Vite für leichte, flexible Frontends. Angular für große Teams mit klarer Struktur und vielen eingebauten Konventionen. |
| „Wann AI-Agenten einsetzen?“ | Für Analyse, Boilerplate, Tests, Refactoring, Dokumentation und wiederholbare Änderungen. Kritische Architektur- und Security-Entscheidungen immer selbst prüfen. |
Merksatz: Ein guter Entwickler erkennt nicht nur, welcher Code funktioniert, sondern warum er funktioniert, wo er brechen kann und wie man ihn später sicher erweitert.
Empfohlene Reihenfolge
- Git, Terminal, Java, Node.js und IDE sicher beherrschen.
- Ein kleines Backend mit Spring Boot erstellen.
- Ein kleines Frontend mit React/Vite oder Angular erstellen.
- Beide über REST/OpenAPI verbinden.
- Codex und Claude Code für konkrete Aufgaben einsetzen: Analyse, Tests, Refactoring, Feature-Implementierung.
- Danach fortgeschrittene Enterprise-Themen lernen: Auth, Observability, CI/CD, Container, Kubernetes, Microservices, Eventing.
2. Grundsetup für Entwickler
2.1 Betriebssystem und Terminal
Nutze eine Umgebung, in der du Shell-Kommandos zuverlässig ausführen kannst.
- macOS/Linux: Terminal, zsh/bash, Homebrew oder Paketmanager.
- Windows: PowerShell plus optional WSL2. Für Java- und Frontend-Projekte ist WSL2 oft angenehmer, weil viele Tools Linux-first dokumentiert sind.
- Git: Pflicht. Jede Agenten-Aufgabe startet und endet mit einem Git-Checkpoint.
git --version
git config --global user.name "Dein Name"
git config --global user.email "deine.email@example.com"2.2 Java Setup
Für Enterprise-Projekte nimm eine LTS-Version. Aktuell sinnvoll:
- Java 25 LTS für neue Projekte, wenn dein Team/Hosting es unterstützt.
- Java 21 LTS für Organisationen, die langsamer migrieren oder noch auf bestehende Plattformen achten müssen.
java --version
javac --versionEmpfohlene Tools:
- Maven oder Gradle
- IntelliJ IDEA oder VS Code
- Docker Desktop oder Podman
- HTTP Client: IntelliJ HTTP Client, curl, httpie, Postman oder Bruno
- Datenbank lokal: PostgreSQL über Docker Compose
2.3 Node.js und Frontend Setup
Für Frontend-Projekte nimm eine LTS-Version von Node.js. Nutze am besten einen Version Manager:
# Beispiel mit nvm
nvm install --lts
nvm use --lts
node --version
npm --versionAlternativen:
pnpmfür schnelle Monoreposnpm workspacesfür einfache Multi-Package-Repositoriescorepackzur Verwaltung von Paketmanagern
corepack enable2.4 IDE Setup
Mindest-Extensions:
- Java: Java Extension Pack oder IntelliJ Java-Unterstützung
- Spring Boot: Spring Tools, Spring Boot Dashboard
- Frontend: ESLint, Prettier, TypeScript, Tailwind CSS IntelliSense
- Docker: Docker Extension
- AI Tools: Codex IDE Extension, Claude Code Extension oder CLI
2.5 Repository-Grundregeln
Vor jeder AI-Agent-Aufgabe:
git status
git checkout -b feature/kurze-beschreibung
git add . && git commit -m "checkpoint before ai task"Nach jeder AI-Agent-Aufgabe:
git diff
git status
mvn test
npm test
npm run build
git add . && git commit -m "implement feature xyz"3. Codex Lernpfad
Erklärung: Warum Codex und Claude Code nicht wie normale Chatbots genutzt werden sollten
Ein Coding-Agent arbeitet am besten, wenn du ihm eine konkrete Aufgabe im Kontext eines Repositories gibst. Statt allgemein zu fragen „Baue mir ein Backend“, solltest du erklären, welches Modul geändert werden soll, welche Regeln gelten, welche Tests existieren und welches Ergebnis du erwartest. Je präziser der Arbeitsrahmen ist, desto kleiner wird das Risiko, dass der Agent zu viel umbaut oder Architekturentscheidungen erfindet.
Schlechte Aufgabe:
Baue mir eine komplette Enterprise-App.
Gute Aufgabe:
Analysiere das bestehende Spring-Boot-Projekt. Ergänze nur im Modul customer eine POST-Route zum Anlegen eines Kunden. Verwende vorhandene DTO-Konventionen, schreibe einen Service-Test und ändere keine Security-Konfiguration. Zeige mir am Ende alle geänderten Dateien und erkläre die wichtigsten Entscheidungen.
Warum ist die zweite Aufgabe besser? Sie begrenzt den Arbeitsbereich, nennt die Technologie, definiert die erwartete Änderung, fordert Tests und verhindert unnötige Umbauten. Genau so solltest du Codex und Claude Code in echten Projekten einsetzen: kleine, überprüfbare Schritte statt riesige unkontrollierte Prompts.
| Agenten-Regel | Erklärung |
|---|---|
| Immer mit Git-Status starten | So siehst du, ob schon lokale Änderungen existieren. |
| Eine Aufgabe pro Prompt | Vermischt du API, UI, Security und Deployment, wird der Diff schwer prüfbar. |
| Tests verlangen | Ohne Test erzeugt der Agent oft Code, der syntaktisch gut aussieht, aber fachlich nicht abgesichert ist. |
| Diff lesen | Du musst verstehen, ob der Agent Nebenwirkungen eingebaut hat. |
| Keine Secrets einfügen | API-Keys, Passwörter und Tokens gehören nie in Prompts oder Codebeispiele. |
3.1 Was ist Codex?
Codex ist ein Coding-Agent von OpenAI. Er kann lokal in deinem Projektverzeichnis arbeiten, Code lesen, Dateien ändern und Kommandos ausführen. Du kannst ihn über CLI, IDE oder Cloud-Oberflächen verwenden, abhängig von deinem Plan und Setup.
3.2 Installation
macOS/Linux:
curl -fsSL https://chatgpt.com/codex/install.sh | sh
codexWindows PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
codexnpm oder Homebrew:
npm install -g @openai/codex
brew install --cask codexBeim ersten Start meldest du dich mit ChatGPT-Account oder API-Key an.
3.3 Erste Aufgaben mit Codex
Starte im Projektordner:
cd mein-projekt
codexGute erste Prompts:
Analysiere dieses Projekt. Erkläre mir die Architektur, die wichtigsten Ordner und wie ich es lokal starte.
Finde kleine Bugs oder inkonsistente Patterns. Ändere noch nichts. Gib mir zuerst einen Plan.
Schreibe Tests für den Auth-Service. Führe die Tests aus und behebe nur Fehler, die direkt mit deinen Tests zusammenhängen.
3.4 Codex Skill-Level
Level 1: Lesen und erklären
Ziel: Du lernst, Codex als Codebase-Erklärer zu nutzen.
Aufgaben:
- Projektstruktur erklären lassen.
- Startbefehle identifizieren lassen.
- Datenfluss durch Controller, Service und Repository erklären lassen.
- Teststrategie des Projekts zusammenfassen lassen.
Prompt:
Erkläre die Codebase aus Sicht eines neuen Entwicklers. Fokus: Starten, Architektur, wichtigste Module, Risiken. Keine Dateien ändern.
Level 2: Kleine Änderungen
Ziel: Codex darf kleine, reversible Änderungen durchführen.
Aufgaben:
- README aktualisieren.
- Lint-Fehler beheben.
- DTO-Feld ergänzen.
- Logging verbessern.
- Kleine Unit Tests schreiben.
Prompt:
Füge im bestehenden Stil ein Feld `status` zum Order-DTO hinzu. Passe Mapping, Tests und Dokumentation an. Mache keine Architekturänderungen.
Level 3: Feature-Implementierung
Ziel: Codex implementiert ein kleines Feature Ende-zu-Ende.
Beispiel: Customer Notes in CRM-App.
Prompt:
Implementiere Customer Notes:
- Backend: Entity, Repository, Service, REST Controller, Validation, Tests
- Datenbank: Flyway Migration
- API: OpenAPI aktualisieren
- Frontend: Liste, Formular, API Client, Fehleranzeige
Arbeite in kleinen Schritten. Erstelle zuerst einen Plan und warte auf Review.
Level 4: Refactoring
Ziel: Codex darf bestehende Struktur verbessern, aber nur mit Tests.
Prompt:
Refactore den Payment-Service in kleinere Methoden. Verhalten darf sich nicht ändern. Schreibe oder erweitere Tests vor der Änderung. Zeige mir danach die wichtigsten Diffs.
Level 5: Migrationen
Ziel: Codex hilft bei Framework- und Versionsmigrationen.
Beispiele:
- Java 21 zu Java 25 prüfen.
- Spring Boot Major Upgrade vorbereiten.
- Angular Update durchführen.
- JavaScript zu TypeScript migrieren.
Prompt:
Untersuche, was für ein Upgrade auf die aktuelle Spring-Boot-Version nötig wäre. Ändere noch nichts. Erstelle eine Migrationsliste mit Risiken, betroffenen Dateien und Testplan.
3.5 Codex Best Practices
- Immer im Git-Branch arbeiten.
- Niemals große Aufgaben ohne Plan starten.
- Erst Analyse, dann Änderung.
- Secrets niemals in Prompts schreiben.
- Nach jeder Änderung Tests laufen lassen.
- Diffs manuell prüfen.
- Agent nicht direkt auf Produktion oder echte Kundendaten loslassen.
- Aufgaben klein schneiden: ein Feature, ein Bug, ein Refactoring.
3.6 Codex Einsatz in Java-Projekten
Gute Codex-Aufgaben:
- Spring Boot Controller + Service + Test generieren.
- Flyway Migration schreiben.
- Integrationstest mit Testcontainers ergänzen.
- Security-Konfiguration erklären.
- Circular Dependencies finden.
- N+1 Query-Probleme suchen.
- OpenAPI-Dokumentation aktualisieren.
Schlechtere Aufgaben:
- Unklare „mach alles besser“-Prompts.
- Große Microservice-Zerlegung ohne Architekturkontext.
- Security-Änderungen ohne Review.
- Datenbankmigrationen ohne Backup- und Rollback-Plan.
4. Claude Code Lernpfad
4.1 Was ist Claude Code?
Claude Code ist ein agentisches Coding-Tool von Anthropic. Es kann Codebases lesen, Dateien editieren, Kommandos ausführen und sich in Terminal, IDE, Desktop, Web und Tooling integrieren.
4.2 Installation
macOS/Linux/WSL:
curl -fsSL https://claude.ai/install.sh | bash
claudeWindows PowerShell:
irm https://claude.ai/install.ps1 | iex
claudeHomebrew:
brew install --cask claude-codeWinGet:
winget install Anthropic.ClaudeCodeBeim ersten Start meldest du dich an:
claudeIn einer laufenden Session kannst du dich neu anmelden:
/login
4.3 Erste Aufgaben mit Claude Code
Erkläre dieses Repository. Welche Module gibt es, wie starte ich Backend und Frontend, welche Tests existieren?
Erstelle einen Plan für ein neues Feature: Task-Kommentare. Nenne betroffene Dateien, Datenmodell, API und Tests. Noch nichts ändern.
Implementiere nur den Backend-Teil aus deinem Plan. Schreibe Tests und führe sie aus.
4.4 Claude Code in VS Code
Die VS-Code-Erweiterung ist praktisch, wenn du Änderungen lieber visuell prüfst:
- Dateien und Zeilenbereiche per
@file.ts#5-10referenzieren. - Plan prüfen, bevor Änderungen akzeptiert werden.
- Diffs side-by-side ansehen.
- Mehrere Sessions parallel führen.
- Bei Bedarf CLI im integrierten Terminal nutzen.
4.5 Claude Code Skill-Level
Level 1: Orientierung
- Projekt erklären lassen.
- Architekturdiagramm als Text erstellen lassen.
- „Wie starte ich lokal?“ dokumentieren lassen.
Level 2: Navigation
- Relevante Dateien zu einem Feature finden.
- Call-Flow erklären lassen.
- Fehler anhand Stacktrace lokalisieren lassen.
Level 3: Umsetzung
- Tests schreiben.
- Kleine Features bauen.
- UI-Komponenten ergänzen.
- API-Client anpassen.
Level 4: Agentische Workflows
- Issue aus Ticket-System lesen.
- Pull Request vorbereiten.
- CI-Fehler analysieren.
- Migrationsplan schreiben.
Level 5: Tool-Integration über MCP
Claude Code kann über MCP mit externen Tools verbunden werden, zum Beispiel Issue Tracker, Datenbanken, Monitoring, Figma oder interne APIs. Das ist stark, aber sicherheitskritisch. Verbinde nur Tools, denen du vertraust, und gib nur minimale Rechte.
4.6 Claude Code Best Practices
- Bei großen Aufgaben zuerst einen Plan verlangen.
- Diffs in der IDE prüfen.
- Tests immer ausführen lassen.
- Für Architekturfragen klare Randbedingungen geben.
- Für MCP: Least Privilege, getrennte Tokens, Audit Logs.
- Für Datenbanken: Nur lokale/dev Datenbanken verbinden, niemals ungeprüft Produktion.
5. Codex vs. Claude Code: Wann welches Tool?
| Situation | Codex | Claude Code |
|---|---|---|
| Terminal-basierte Arbeit im Repo | Sehr gut | Sehr gut |
| IDE-Diff und visuelle Review | Sehr gut mit IDE Extension | Sehr gut mit VS-Code-Extension |
| Lange Analyse großer Codebases | Sehr gut | Sehr gut |
| Plan-first Umsetzung | Sehr gut | Sehr gut |
| MCP-/Tool-Ökosystem | Gut, je nach Setup | Sehr stark über Claude Code MCP |
| OpenAI/ChatGPT-Workflow | Sehr passend | Nicht relevant |
| Anthropic/Claude-Team-Workflow | Nicht relevant | Sehr passend |
| Java Backend Boilerplate | Gut | Gut |
| Frontend-Komponenten | Gut | Gut |
| Sicherheitskritische Änderungen | Nur mit Review | Nur mit Review |
Praktische Empfehlung: Lerne beide. Nutze sie nicht religiös. Entscheidend ist dein Workflow:
- Für schnelle lokale Aufgaben: CLI.
- Für UI-Diffs: IDE Extension.
- Für externe Tool-Kontexte: MCP/Connectors, aber mit strenger Rechtevergabe.
- Für Architektur: erst Plan, dann kleine Schritte.
6. AI-Coding-Workflow für echte Projekte
6.1 Der sichere Standardablauf
1. Git-Branch erstellen
2. Agent soll Codebase analysieren
3. Agent soll Plan erstellen
4. Plan prüfen
5. Agent implementiert kleinen Teil
6. Tests ausführen
7. Diff prüfen
8. Commit
9. Nächster kleiner Teil
6.2 Aufgaben richtig schneiden
Schlecht:
Baue mir eine komplette Enterprise-App.
Gut:
Implementiere im bestehenden Spring-Boot-Projekt einen read-only REST-Endpunkt GET /api/customers/{id}. Nutze bestehende Patterns, schreibe Unit- und Integrationstest, ändere keine Security-Konfiguration.
6.3 Agenten-Kontext geben
Guter Kontext enthält:
- Ziel des Features
- Akzeptanzkriterien
- betroffene Schichten
- Nicht-Ziele
- Testanforderungen
- Coding-Standards
- Sicherheitsgrenzen
Beispiel:
Ziel: Benutzer können Bestellungen nach Status filtern.
Akzeptanzkriterien:
- GET /api/orders?status=OPEN gibt nur offene Bestellungen zurück
- Ungültiger Status liefert 400
- Integrationstest mit Testdaten
Nicht-Ziele:
- Keine UI-Änderung
- Keine Änderung an Auth
Stil:
- Bestehende Controller/Service/Repository-Struktur beibehalten
6.4 Review-Fragen nach Agentenarbeit
Nach jeder Änderung fragst du:
- Wurden unnötige Dateien geändert?
- Wurde Fachlogik verändert, obwohl nur Refactoring geplant war?
- Sind Tests sinnvoll oder nur oberflächlich?
- Gibt es Security-Probleme?
- Gibt es neue Abhängigkeiten?
- Sind Fehlermeldungen und Validierung sauber?
- Funktioniert Build lokal?
7. Java Enterprise Lernpfad
7.1 Was bedeutet Java Enterprise heute?
Java Enterprise bedeutet heute nicht nur „Java EE“. Es meint produktionsfähige Unternehmenssoftware mit:
- stabiler JVM-Plattform
- Backend-Frameworks wie Spring Boot, Jakarta EE, Quarkus oder Micronaut
- Datenbanken und Transaktionen
- Security und Identity Provider
- REST, GraphQL oder Messaging APIs
- Tests, Observability, CI/CD
- Container und Cloud Deployment
7.2 Priorisierte Frameworks
| Framework | Wann lernen? | Einsatz |
|---|---|---|
| Spring Boot | zuerst | Standard für viele Enterprise-Backends, sehr großes Ökosystem |
| Jakarta EE | danach | Standardisierte Enterprise-APIs, App-Server, Portabilität |
| Quarkus | danach | Cloud-native, Kubernetes, schnelle Dev-Loops, GraalVM-freundlich |
| Micronaut | danach | Microservices, geringe Startzeit, Compile-Time DI |
| Helidon/Open Liberty | optional | Jakarta/MicroProfile-orientierte Enterprise-Stacks |
7.3 Java-Grundlagen für Enterprise
Du solltest sicher beherrschen:
- Klassen, Interfaces, Records, Enums
- Collections und Streams
- Exceptions und Error Handling
- Generics
- Concurrency-Grundlagen
- Optional und Null-Safety Patterns
- Date/Time API
- Maven/Gradle
- JUnit 5, AssertJ, Mockito
- HTTP, JSON, REST
- SQL und Transaktionen
7.4 Enterprise-Konzepte
Lerne diese Konzepte parallel zur Praxis:
- Layered Architecture
- Hexagonal Architecture / Ports and Adapters
- Domain-Driven Design Basics
- DTO vs Entity
- Validation
- Transactions
- Security: Authentication vs Authorization
- OAuth2/OIDC/JWT
- Idempotenz
- Pagination und Filtering
- Caching
- Observability: Logs, Metrics, Traces
- CI/CD und Deployment Pipelines
8. Java Enterprise Projekt starten
Erklärung: Warum ein Java-Enterprise-Projekt mehr ist als ein Controller
Viele Anfänger starten mit einem Controller und schreiben dort direkt Datenbankzugriffe, Validierung und Geschäftslogik hinein. Das funktioniert für kleine Demos, wird aber schnell unwartbar. Ein Enterprise-Projekt trennt Verantwortlichkeiten: Der Controller nimmt HTTP-Anfragen an, DTOs beschreiben Eingabe und Ausgabe, der Service enthält Fachlogik, das Repository spricht mit der Datenbank, und Tests sichern das Verhalten ab.
Diese Trennung klingt am Anfang nach mehr Arbeit. Der Vorteil kommt später: Du kannst einzelne Teile austauschen, besser testen, Fehler sauberer behandeln und Sicherheitsregeln zentraler durchsetzen. In größeren Teams ist diese Struktur auch wichtig, damit neue Entwickler schnell verstehen, wo welche Logik hingehört.
| Schicht | Aufgabe | Typischer Fehler |
|---|---|---|
| Controller | HTTP-Endpunkte, Statuscodes, Request/Response | Geschäftslogik direkt im Controller schreiben |
| DTO | Daten nach außen und innen begrenzen | Entity direkt an das Frontend zurückgeben |
| Service | Fachlogik, Transaktionen, Regeln | Datenbankdetails mit UI-Regeln vermischen |
| Repository | Persistenz und Queries | Komplexe Fachentscheidungen in SQL/Repository verstecken |
| Migration | Datenbankschema versionieren | Tabellen manuell in der DB erstellen |
| Test | Verhalten absichern | Nur Happy Path testen |
Praktischer Grundsatz: Wenn du eine Änderung später erklären musst, sollte klar sein, in welcher Schicht sie liegt. Eine neue Validierungsregel gehört meist in DTO/Service. Eine neue URL gehört in den Controller. Eine neue Tabelle gehört in Migration und Entity. Eine neue Darstellung gehört ins Frontend.
8.1 Entscheidungsbaum
Nimm Spring Boot, wenn:
- du schnell produktiv sein willst,
- du viele Beispiele und Libraries brauchst,
- du REST APIs, JPA, Security, Batch, Messaging oder Actuator brauchst,
- Teammitglieder unterschiedliche Erfahrungslevel haben.
Nimm Jakarta EE/Open Liberty/WildFly/Payara, wenn:
- Standard-APIs und App-Server-Portabilität wichtig sind,
- dein Unternehmen bereits App-Server betreibt,
- du Jakarta REST, CDI, JPA, Bean Validation und Transactions standardnah nutzen willst.
Nimm Quarkus, wenn:
- Kubernetes, Container, schnelle Startzeit und Dev Mode wichtig sind,
- du cloud-native Java bauen willst,
- du MicroProfile/Jakarta-nahe APIs magst.
Nimm Micronaut, wenn:
- Compile-Time Dependency Injection,
- Microservices,
- geringe Reflection-Nutzung,
- schnelle Startup-Zeiten wichtig sind.
8.2 Spring Boot Projekt mit Spring Initializr
Empfohlene Auswahl:
- Project: Maven oder Gradle
- Language: Java
- Java: 25 oder 21 LTS
- Packaging: Jar
- Dependencies:
- Spring Web
- Spring Validation
- Spring Data JPA
- PostgreSQL Driver
- Spring Security
- OAuth2 Resource Server
- Actuator
- Flyway Migration
- Testcontainers
- Lombok optional
Beispiel mit curl:
curl https://start.spring.io/starter.zip \
-d dependencies=web,validation,data-jpa,postgresql,security,oauth2-resource-server,actuator,flyway \
-d type=maven-project \
-d language=java \
-d javaVersion=25 \
-d bootVersion=4.1.0 \
-d groupId=com.example \
-d artifactId=enterprise-api \
-o enterprise-api.zip
unzip enterprise-api.zip -d enterprise-api
cd enterprise-api
./mvnw spring-boot:runWenn die gewünschte Spring-Boot-Version in deinem Unternehmen festgelegt ist, nutze diese statt blind die neueste Version.
8.3 Alternative: Quarkus Projekt starten
quarkus create app com.example:enterprise-api \
--extension='rest-jackson,hibernate-orm-panache,jdbc-postgresql,flyway,smallrye-openapi,smallrye-health'
cd enterprise-api
quarkus devOder mit Maven Plugin:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=enterprise-api \
-Dextensions='rest-jackson,hibernate-orm-panache,jdbc-postgresql,flyway,smallrye-openapi'8.4 Alternative: Micronaut Projekt starten
mn create-app com.example.enterprise-api \
--features=data-jdbc,postgres,flyway,security-jwt,management,openapi,testcontainers \
--build=maven \
--lang=java
cd enterprise-api
./mvnw mn:run8.5 Empfohlene Spring-Boot-Projektstruktur
enterprise-api/
src/main/java/com/example/enterprise/
EnterpriseApiApplication.java
config/
SecurityConfig.java
OpenApiConfig.java
customer/
Customer.java
CustomerController.java
CustomerService.java
CustomerRepository.java
CustomerMapper.java
dto/
CustomerRequest.java
CustomerResponse.java
order/
Order.java
OrderController.java
OrderService.java
OrderRepository.java
dto/
common/
error/
ApiError.java
GlobalExceptionHandler.java
pagination/
validation/
src/main/resources/
application.yml
db/migration/
V1__init.sql
src/test/java/com/example/enterprise/
customer/
order/
8.6 Minimaler Controller-Service-Repository Flow
@RestController
@RequestMapping("/api/customers")
class CustomerController {
private final CustomerService service;
CustomerController(CustomerService service) {
this.service = service;
}
@GetMapping("/{id}")
CustomerResponse getById(@PathVariable UUID id) {
return service.getById(id);
}
}@Service
class CustomerService {
private final CustomerRepository repository;
private final CustomerMapper mapper;
CustomerService(CustomerRepository repository, CustomerMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}
@Transactional(readOnly = true)
CustomerResponse getById(UUID id) {
return repository.findById(id)
.map(mapper::toResponse)
.orElseThrow(() -> new NotFoundException("Customer not found"));
}
}8.7 Konfiguration mit application.yml
spring:
application:
name: enterprise-api
datasource:
url: jdbc:postgresql://localhost:5432/enterprise
username: enterprise
password: enterprise
jpa:
hibernate:
ddl-auto: validate
open-in-view: false
flyway:
enabled: true
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus8.8 Lokale Datenbank mit Docker Compose
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: enterprise
POSTGRES_USER: enterprise
POSTGRES_PASSWORD: enterprise
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:Start:
docker compose up -d
./mvnw spring-boot:run8.9 AI-Prompt zum Erstellen eines Spring Features
Du bist Senior Java/Spring Boot Entwickler.
Implementiere ein Customer-Modul im bestehenden Projekt.
Anforderungen:
- Entity: Customer mit id, name, email, status, createdAt
- REST: POST /api/customers, GET /api/customers/{id}, GET /api/customers
- Validation: email gültig, name nicht leer
- Persistence: Spring Data JPA
- Migration: Flyway SQL
- Error Handling: vorhandenen GlobalExceptionHandler nutzen
- Tests: Service Unit Tests und Controller Integration Test
Regeln:
- Bestehende Projektstruktur beibehalten
- Keine unnötigen Dependencies hinzufügen
- Erst Plan zeigen, dann implementieren
9. Backend-Architektur für Enterprise-Projekte
9.1 Layered Architecture
Klassisch und verständlich:
Controller -> Service -> Repository -> Database
Gut für:
- CRUD APIs
- kleine bis mittlere Teams
- schnelle Produktentwicklung
Risiko:
- Fachlogik verteilt sich schnell zwischen Service, Entity und Controller.
- Bei wachsender Komplexität wird es schwer, Domänenregeln sauber zu halten.
9.2 Hexagonal Architecture
Bessere Trennung zwischen Domain und Infrastruktur:
Inbound Adapter: REST Controller
Application Service: Use Cases
Domain: Entities, Value Objects, Rules
Outbound Port: Repository Interface
Outbound Adapter: JPA Repository
Beispielstruktur:
customer/
domain/
Customer.java
CustomerStatus.java
application/
CreateCustomerUseCase.java
GetCustomerUseCase.java
port/in/
CreateCustomerCommand.java
port/out/
CustomerRepositoryPort.java
adapter/in/web/
CustomerController.java
adapter/out/jpa/
JpaCustomerRepository.java
CustomerJpaEntity.java
Gut für:
- komplexe Domänen
- langfristige Wartung
- Tests ohne Datenbank
- Austausch von Infrastruktur
9.3 Modularer Monolith
Für viele Enterprise-Projekte ist ein modularer Monolith besser als sofort Microservices.
app/
customer-module
order-module
billing-module
notification-module
Regeln:
- Module kommunizieren über definierte APIs.
- Keine wilden Cross-Imports.
- Pro Modul eigene Tests.
- Gemeinsame „common“-Module klein halten.
9.4 Microservices erst später
Nimm Microservices erst, wenn du wirklich brauchst:
- unabhängige Deployments
- getrennte Skalierung
- getrennte Teams
- klare Domänengrenzen
- robuste DevOps-Reife
Sonst erzeugst du nur verteilte Komplexität: Netzwerkfehler, Datenkonsistenz, Observability, Deployment-Abhängigkeiten, Versionierung.
9.5 Datenmodell und Migrationen
Regeln:
- Keine
ddl-auto=createin Enterprise. - Schemaänderungen über Flyway oder Liquibase.
- Jede Migration muss vorwärtskompatibel geplant werden.
- Datenmigrationen separat testen.
- Rollback-Strategie dokumentieren.
9.6 API Design
REST-Konventionen:
GET /api/customers
GET /api/customers/{id}
POST /api/customers
PUT /api/customers/{id}
PATCH /api/customers/{id}/status
DELETE /api/customers/{id}
Achte auf:
- konsistente Fehlerantworten
- Pagination
- Sorting
- Filtering
- Versionierung
- OpenAPI-Dokumentation
- Idempotenz bei kritischen Operationen
10. Frontend Lernpfad
10.1 Reihenfolge
- HTML, CSS, JavaScript
- TypeScript
- Browser APIs und HTTP
- Komponentenmodell
- Routing
- Forms und Validation
- State Management
- API Clients
- Testing
- Performance und Accessibility
- Build, Deployment und Monitoring
10.2 TypeScript zuerst ernst nehmen
Enterprise-Frontend ohne TypeScript ist riskant. Lerne:
- primitive Typen
- Interfaces und Types
- Union Types
- Generics
- Utility Types
- Type Guards
unknownstattany- DTO-Typen aus OpenAPI generieren
Beispiel:
export type CustomerStatus = 'ACTIVE' | 'INACTIVE' | 'BLOCKED';
export interface CustomerResponse {
id: string;
name: string;
email: string;
status: CustomerStatus;
createdAt: string;
}10.3 Frontend-Framework-Auswahl
| Stack | Wann nehmen? | Typischer Einsatz |
|---|---|---|
| React + Vite | flexibel, schnell, viele Libraries | SPAs, Dashboards, interne Tools |
| Angular | großes Team, klare Struktur, Enterprise-Standards | große Unternehmensanwendungen |
| Next.js | React mit SSR/SSG, App Router, Fullstack-Optionen | Web-Apps, Portale, SEO, SaaS |
| Vue + Vite | leichter Einstieg, gute DX | kleinere bis mittlere Apps |
| Svelte/SvelteKit | kompakt, performant | moderne Apps, wenn Team Erfahrung hat |
10.4 Enterprise-Frontend-Bausteine
- Routing
- Layout-System
- Design System oder UI Library
- Auth Guard
- API Client
- Error Handling
- Toasts/Notifications
- Forms
- Tabellen mit Pagination/Filtering
- Internationalisierung
- Accessibility
- Unit Tests
- E2E Tests
- Build Pipeline
11. Frontend-Stacks erstellen
Erklärung: Wie du den passenden Frontend-Stack auswählst
Frontend-Stacks unterscheiden sich weniger darin, ob sie „gut“ oder „schlecht“ sind, sondern darin, welche Art von Projekt und Team sie unterstützen. Für ein kleines Dashboard ist React mit Vite sehr schnell und flexibel. Für ein großes internes Enterprise-Portal mit vielen Formularen, Routing-Regeln und klaren Team-Konventionen kann Angular sehr angenehm sein. Next.js lohnt sich, wenn SEO, serverseitiges Rendering, Content-Seiten oder Fullstack-Routing wichtig sind. Vue ist gut, wenn du eine leicht zugängliche, komponentenbasierte Lösung möchtest.
| Situation | Empfehlung | Warum |
|---|---|---|
| Kleines bis mittleres SPA | React + Vite | Schnell eingerichtet, flexibel, großer Ökosystem-Vorteil |
| Großes Enterprise-Frontend | Angular | Klare Struktur, CLI, Routing, Forms, DI und Team-Konventionen |
| SEO-starke Website/App | Next.js | Server Rendering, Routing und Content-Struktur sind stärker integriert |
| Einfacher Einstieg mit Komponenten | Vue | Gut lesbar, geringe Einstiegshürde |
| Designsystem für mehrere Apps | React oder Angular + Storybook | Komponenten können dokumentiert und wiederverwendet werden |
Wichtig: Die Stack-Wahl löst nicht automatisch UX-Probleme. Du brauchst trotzdem gute Formularführung, Ladezustände, Fehlertexte, Barrierefreiheit, responsive Layouts und saubere API-Verträge.
11.1 React + Vite + TypeScript
npm create vite@latest enterprise-web -- --template react-ts
cd enterprise-web
npm install
npm run devEmpfohlene Dependencies:
npm install react-router-dom @tanstack/react-query zod axios
npm install -D vitest @testing-library/react @testing-library/jest-dom eslint prettierStruktur:
enterprise-web/
src/
app/
App.tsx
router.tsx
providers.tsx
features/
customers/
api/customerApi.ts
components/CustomerForm.tsx
pages/CustomerListPage.tsx
types.ts
shared/
api/httpClient.ts
components/
errors/
config/env.ts
main.tsx
Minimaler API Client:
import axios from 'axios';
export const httpClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080/api',
});React Query Beispiel:
import { useQuery } from '@tanstack/react-query';
import { httpClient } from '../../shared/api/httpClient';
export function useCustomers() {
return useQuery({
queryKey: ['customers'],
queryFn: async () => {
const response = await httpClient.get('/customers');
return response.data;
},
});
}11.2 Angular Enterprise App
npm install -g @angular/cli
ng new enterprise-web --routing --style=scss
cd enterprise-web
ng serveEmpfohlene Struktur:
src/app/
core/
auth/
http/
guards/
shared/
ui/
pipes/
features/
customers/
pages/
components/
data-access/
models/
app.routes.ts
Service-Beispiel:
@Injectable({ providedIn: 'root' })
export class CustomerApiService {
private readonly http = inject(HttpClient);
private readonly baseUrl = '/api/customers';
getCustomers(): Observable<CustomerResponse[]> {
return this.http.get<CustomerResponse[]>(this.baseUrl);
}
}Angular passt besonders gut, wenn dein Team klare Konventionen, Dependency Injection, Router, Forms, HTTP und Testing in einem Framework möchte.
11.3 Next.js App
npx create-next-app@latest enterprise-portal
cd enterprise-portal
npm run devTypische Struktur:
app/
layout.tsx
page.tsx
customers/
page.tsx
components/
lib/
api.ts
Nimm Next.js, wenn du brauchst:
- SSR oder SSG
- SEO
- serverseitige Datenladung
- Auth im Web-Portal
- Landing Pages plus App-Funktionen
11.4 Vue + Vite + TypeScript
npm create vue@latest enterprise-web
cd enterprise-web
npm install
npm run devGute Wahl, wenn:
- du schnellen Einstieg willst,
- das Team Vue kennt,
- die App weniger komplex ist als ein großes Angular-Projekt,
- du trotzdem TypeScript, Router und State sauber einsetzen willst.
11.5 UI Libraries
| Framework | Libraries |
|---|---|
| React | MUI, Chakra UI, Ant Design, shadcn/ui, Tailwind CSS |
| Angular | Angular Material, PrimeNG, NG-ZORRO, Tailwind CSS |
| Vue | Vuetify, PrimeVue, Naive UI, Tailwind CSS |
Enterprise-Regel: Nimm nicht zu viele UI Libraries gleichzeitig. Entscheide dich früh für Design Tokens, Spacing, Farben, Form Controls und Tabellenverhalten.
11.6 Frontend AI-Prompt
Du bist Senior Frontend Entwickler.
Erstelle im bestehenden React/Vite/TypeScript Projekt ein Customer Feature.
Anforderungen:
- Route /customers
- Tabelle mit Name, Email, Status
- Lade Daten über vorhandenen httpClient
- Loading, Empty State, Error State
- Komponente testbar halten
- Keine neue UI Library hinzufügen
- Erst Plan, dann Umsetzung
12. Fullstack-Integration: Java Backend + Frontend
12.1 Empfohlene Architektur lokal
Browser -> Frontend Dev Server :5173
Frontend -> Backend API :8080
Backend -> PostgreSQL :5432
12.2 CORS im Backend
Für lokale Entwicklung:
@Configuration
class WebConfig {
@Bean
WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*");
}
};
}
}Für Produktion CORS restriktiver setzen.
12.3 API Contract mit OpenAPI
Backend generiert OpenAPI, Frontend generiert Types:
npm install -D openapi-typescript
npx openapi-typescript http://localhost:8080/v3/api-docs -o src/shared/api/schema.tsVorteile:
- Frontend DTOs passen zum Backend.
- Breaking Changes werden schneller sichtbar.
- Weniger manuelle Typfehler.
12.4 Proxy statt CORS im Frontend
Vite Beispiel:
export default defineConfig({
server: {
proxy: {
'/api': 'http://localhost:8080',
},
},
});Dann kann das Frontend /api/customers nutzen.
12.5 Fullstack-Monorepo
enterprise-app/
backend/
pom.xml
src/
frontend/
package.json
src/
docker-compose.yml
_intern/sources/_intern\sources\README.md
Root README sollte enthalten:
# Start DB
docker compose up -d
# Start backend
cd backend && ./mvnw spring-boot:run
# Start frontend
cd frontend && npm run dev12.6 CI Pipeline Beispiel
name: build
on: [push, pull_request]
jobs:
backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
- run: cd backend && ./mvnw test
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
- run: cd frontend && npm ci
- run: cd frontend && npm test
- run: cd frontend && npm run build13. Testing, Security, Observability und DevOps
13.1 Backend Testing
| Testtyp | Tool | Ziel |
|---|---|---|
| Unit Test | JUnit, AssertJ, Mockito | Fachlogik schnell testen |
| Slice Test | @WebMvcTest, @DataJpaTest |
einzelne Spring-Schicht testen |
| Integration Test | Spring Boot Test, Testcontainers | API + DB realitätsnah testen |
| Contract Test | OpenAPI, Pact | Backend/Frontend Vertrag prüfen |
| E2E | Playwright/Cypress | User Flow prüfen |
13.2 Frontend Testing
- Unit Tests mit Vitest/Jest
- Component Tests mit Testing Library
- E2E Tests mit Playwright
- Accessibility Checks
- Visual Regression optional
13.3 Security Basics
Backend:
- OAuth2/OIDC statt eigener Passwortlogik, wenn möglich
- JWT validieren, nicht nur decodieren
- Rollen und Berechtigungen serverseitig prüfen
- Input Validation
- Rate Limiting für kritische Endpunkte
- Audit Logging
- Secrets über Vault/Cloud Secret Manager
Frontend:
- Tokens sicher behandeln
- Keine Secrets im Frontend
- XSS vermeiden
- CSRF je nach Auth-Strategie beachten
- Auth Guards nur UX, nicht Sicherheit
13.4 Observability
Mindestens:
- strukturierte Logs
- Health Checks
- Metrics über Actuator/Micrometer
- Tracing mit OpenTelemetry
- Error Tracking
- Dashboards und Alerts
13.5 DevOps Basics
- Dockerfile für Backend und Frontend
- Docker Compose für lokale Infrastruktur
- CI mit Tests und Build
- Security Scans für Dependencies
- Migrations-Check in Pipeline
- Deployment: Kubernetes, Cloud Run, ECS, Azure Container Apps oder klassische VM, je nach Unternehmen
14. 12-Wochen-Roadmap
Woche 1: Tooling und Grundlagen
- Git Branching, Commits, Diffs
- Java LTS installieren
- Node LTS installieren
- IDE einrichten
- Codex und Claude Code installieren
- Erstes Repo analysieren lassen
Ergebnis: Du kannst lokale Projekte starten und Agenten kontrolliert nutzen.
Woche 2: Java und Spring Boot Basics
- Spring Boot Projekt erstellen
- Controller, Service, Repository
- DTOs und Validation
- Fehlerbehandlung
- PostgreSQL via Docker
Ergebnis: CRUD Backend läuft lokal.
Woche 3: Tests und Datenbankmigrationen
- JUnit 5
- Mockito
- Spring Boot Integration Tests
- Testcontainers
- Flyway
Ergebnis: Backend ist testbar und DB-Schema versioniert.
Woche 4: Security
- Spring Security Basics
- OAuth2 Resource Server
- JWT
- Rollen und Berechtigungen
- CORS
Ergebnis: API ist abgesichert.
Woche 5: TypeScript und React/Vite
- TypeScript Grundlagen
- React Komponenten
- Router
- API Client
- React Query
Ergebnis: Frontend ruft Backend auf.
Woche 6: Angular Alternative
- Angular CLI
- Components
- Services
- Router
- Reactive Forms
- HTTP Client
Ergebnis: Du kannst entscheiden, ob React oder Angular besser passt.
Woche 7: Fullstack Contract
- OpenAPI generieren
- Frontend Types generieren
- API Fehler anzeigen
- Pagination
- Loading/Error/Empty States
Ergebnis: Frontend und Backend sind vertraglich verbunden.
Woche 8: Architektur
- Modularer Monolith
- Hexagonal Architecture
- DDD Basics
- Module Boundaries
- Refactoring mit Codex/Claude Code
Ergebnis: Projekt bleibt wartbar.
Woche 9: Quarkus und Micronaut Überblick
- Quarkus Dev Mode
- Micronaut DI
- MicroProfile Basics
- Vergleich mit Spring Boot
Ergebnis: Du kennst Alternativen und deren Einsatzfälle.
Woche 10: Observability und DevOps
- Actuator
- Logs
- Metrics
- OpenTelemetry Grundidee
- Dockerfile
- CI Pipeline
Ergebnis: App ist deploy- und beobachtbar.
Woche 11: Agentische Entwicklung
- Codex für Tests und Refactoring
- Claude Code für Multi-File Features
- MCP/Tool-Integration verstehen
- PR Review mit Agenten
Ergebnis: Du nutzt AI-Agenten produktiv und sicher.
Woche 12: Abschlussprojekt
- Eigenes Fullstack-Projekt bauen
- README, Architekturdiagramm, Tests, CI
- Agenten-Prompts dokumentieren
- Code Review durchführen
Ergebnis: Portfolio-fähiges Enterprise-Projekt.
15. Masterprojekt
Baue ein kleines, aber realistisches Enterprise-System: Customer Order Management.
15.1 Funktionen
Backend:
- Customer CRUD
- Order CRUD
- Order Status Workflow
- Auth mit Rollen: ADMIN, USER
- PostgreSQL
- Flyway Migrationen
- OpenAPI
- Actuator
- Tests
Frontend:
- Login-Seite oder Auth-Mock
- Customer-Liste
- Customer-Details
- Order-Liste
- Order erstellen
- Status ändern
- Error/Loading/Empty States
DevOps:
- Docker Compose
- CI Pipeline
- README
- Architekturdiagramm als Mermaid
15.2 Phasen
Phase 1: Backend Skeleton
Erstelle ein Spring-Boot-Projekt für Customer Order Management.
Nutze Java 25, Maven, PostgreSQL, Flyway, Validation, Actuator.
Implementiere noch keine Features. Richte nur Struktur, Docker Compose, README und Health Endpoint ein.
Phase 2: Customer Modul
Implementiere Customer CRUD mit Tests und Flyway Migration.
Nutze die bestehende Projektstruktur.
Erstelle zuerst einen Plan.
Phase 3: Order Modul
Implementiere Order CRUD mit Beziehung zu Customer.
Achte auf Transaktionen, Validierung und sinnvolle Fehlercodes.
Phase 4: Frontend
Erstelle ein React/Vite/TypeScript Frontend.
Baue Customer List, Customer Details und Order List.
Nutze vorhandenen API Client und Loading/Error States.
Phase 5: Integration
Verbinde Frontend und Backend über OpenAPI-generierte Typen.
Prüfe alle API Calls und erstelle eine kurze Dokumentation.
Phase 6: Qualität
Analysiere das Projekt auf Testlücken, Security-Risiken, fehlende Fehlerbehandlung und schlechte Architekturgrenzen.
Erstelle zuerst eine Liste, ändere noch nichts.
16. Beispielprojekt: Customer Order Portal
Dieses Beispielprojekt ist ein kompletter, realistischer Startpunkt für ein Java-Enterprise-Projekt mit Frontend. Du kannst es als Portfolio-Projekt, Lernprojekt oder als Template für echte Kundenprojekte verwenden.
Projektidee: Ein internes Portal, in dem Mitarbeitende Kunden verwalten und Bestellungen anlegen. Das Backend liefert eine REST API, das Frontend konsumiert diese API, und PostgreSQL speichert die Daten.
16.1 Ziel und Lernnutzen
Erklärung: Warum dieses Beispielprojekt gut zum Lernen ist
Das Customer Order Portal ist ein ideales erstes Enterprise-Beispiel, weil es typische Bausteine enthält, ohne fachlich zu kompliziert zu werden. Kunden, Bestellungen, Produkte und Statuswerte kommen in fast jedem Unternehmenssystem vor. Dadurch lernst du nicht nur Syntax, sondern echte Muster: Datenmodellierung, REST-API, Validierung, Fehlerbehandlung, Frontend-Listen, Formulare und Tests.
Du solltest dieses Projekt nicht als „fertige Produktionssoftware“ sehen, sondern als Trainingsbasis. Erst wird ein kleiner vertikaler Schnitt gebaut: Datenbanktabelle, Entity, DTO, Service, Controller, API-Client und UI-Liste. Danach kannst du denselben Schnitt für neue Module wiederholen. Genau diese Wiederholung ist wertvoll, weil Enterprise-Software oft aus vielen ähnlichen, aber fachlich unterschiedlichen Modulen besteht.
| Lernpunkt | Warum er wichtig ist |
|---|---|
| Monorepo-Struktur | Backend und Frontend bleiben gemeinsam dokumentiert und versioniert. |
| Flyway-Migrationen | Datenbankschema ist reproduzierbar und teamfähig. |
| DTOs statt Entities | Du kontrollierst, welche Daten nach außen gehen. |
| Service-Schicht | Fachlogik bleibt unabhängig vom HTTP-Controller. |
| API-Client im Frontend | Frontend-Komponenten müssen nicht wissen, wie URLs genau gebaut werden. |
| Tests | Änderungen können später sicherer refaktoriert werden. |
Typische Erweiterung: Baue zuerst Kunden, dann Bestellungen, dann Produkte. Danach erst Authentifizierung, Rollen und Reporting. So bleibt die Komplexität kontrollierbar.
Nach diesem Projekt kannst du:
- ein Spring-Boot-Backend sauber initialisieren,
- eine typische Enterprise-Struktur mit Controller, Service, Repository, DTOs und Migrationen aufbauen,
- ein React/Vite-Frontend oder alternativ ein Angular-Frontend starten,
- Backend und Frontend über REST verbinden,
- lokale Infrastruktur mit Docker Compose starten,
- Tests ergänzen,
- Codex oder Claude Code gezielt für Analyse, Implementierung, Tests und Refactoring einsetzen.
16.2 Fachlicher Scope
Version 1: Minimal lauffähig
- Kunden anlegen
- Kunden auflisten
- Kunden-Details anzeigen
- Bestellung für Kunden anlegen
- Bestellungen auflisten
- Gesundheitscheck über Actuator
Version 2: Enterprise-Ausbau
- Login/Auth mit Rollen
- Status-Workflow für Orders
- Suche, Filter, Pagination
- OpenAPI-Dokumentation
- Audit-Felder
- Testcontainers
- CI Pipeline
- Dockerfile für Backend und Frontend
16.3 Technologie-Stack
| Bereich | Empfehlung | Warum |
|---|---|---|
| Backend | Java 21 oder 25 + Spring Boot | Enterprise-Standard, viele Integrationen, schnelle API-Entwicklung |
| Build | Maven | einfach, gut dokumentiert, sehr verbreitet |
| Datenbank | PostgreSQL | produktionsnaher SQL-Standard |
| Migrationen | Flyway | Datenbankschema versionieren |
| API | REST + DTOs | klarer Einstieg, leicht testbar |
| Frontend 1 | React + Vite + TypeScript | schnell, flexibel, sehr beliebt |
| Frontend 2 | Angular + TypeScript | starkes Enterprise-Framework mit klarer Struktur |
| Dev Services | Docker Compose | lokale Datenbank reproduzierbar starten |
| AI Tools | Codex + Claude Code | Boilerplate, Tests, Refactoring und Codebase-Analyse beschleunigen |
16.4 Repository-Struktur
Empfohlen ist ein Monorepo mit getrenntem Backend und Frontend:
customer-order-portal/
_intern/sources/_intern\sources\README.md
docs/
architecture.md
api.md
prompts.md
backend/
pom.xml
compose.yaml
src/
main/
java/com/example/portal/
CustomerOrderPortalApplication.java
common/
error/
ApiError.java
GlobalExceptionHandler.java
validation/
customer/
Customer.java
CustomerStatus.java
CustomerRepository.java
CustomerService.java
CustomerController.java
dto/
CustomerCreateRequest.java
CustomerResponse.java
order/
Order.java
OrderStatus.java
OrderRepository.java
OrderService.java
OrderController.java
dto/
OrderCreateRequest.java
OrderResponse.java
resources/
application.yml
db/migration/
V1__init.sql
test/
java/com/example/portal/
customer/
order/
frontend-react/
package.json
vite.config.ts
src/
main.tsx
App.tsx
api/
client.ts
customers.ts
orders.ts
features/
customers/
CustomerList.tsx
CustomerDetail.tsx
CustomerCreateForm.tsx
orders/
OrderList.tsx
OrderCreateForm.tsx
shared/
Loading.tsx
ErrorMessage.tsx
frontend-angular/
angular.json
package.json
src/app/
core/api/
features/customers/
features/orders/
16.5 Projekt initialisieren
Variante A: Backend über Spring Initializr erzeugen
Öffne Spring Initializr im Browser oder nutze curl.
mkdir customer-order-portal
cd customer-order-portal
mkdir docs
curl https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=3.5.0 \
-d baseDir=backend \
-d groupId=com.example \
-d artifactId=customer-order-portal \
-d name=customer-order-portal \
-d packageName=com.example.portal \
-d javaVersion=21 \
-d dependencies=web,validation,data-jpa,postgresql,flyway,actuator,docker-compose \
-o backend.zip
unzip backend.zip
rm backend.zipHinweis: Passe
bootVersionbei Bedarf an die aktuell stabile Version an. Für Enterprise-Teams ist wichtiger, dass alle Teammitglieder dieselbe Version verwenden und sie im Repository dokumentiert ist.
Variante B: React/Vite-Frontend erzeugen
cd customer-order-portal
npm create vite@latest frontend-react -- --template react-ts
cd frontend-react
npm install
npm run devVariante C: Angular-Frontend erzeugen
cd customer-order-portal
npm install -g @angular/cli
ng new frontend-angular --routing --style=scss
cd frontend-angular
ng serve16.6 Backend: compose.yaml
Lege diese Datei in backend/compose.yaml an:
services:
postgres:
image: postgres:16
container_name: customer-order-postgres
environment:
POSTGRES_DB: customer_order
POSTGRES_USER: app
POSTGRES_PASSWORD: app
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d customer_order"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:Start:
cd backend
docker compose up -d
./mvnw spring-boot:run16.7 Backend: application.yml
spring:
application:
name: customer-order-portal
datasource:
url: jdbc:postgresql://localhost:5432/customer_order
username: app
password: app
jpa:
hibernate:
ddl-auto: validate
open-in-view: false
properties:
hibernate:
format_sql: true
flyway:
enabled: true
docker:
compose:
lifecycle-management: start-only
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,info,metrics
logging:
level:
org.hibernate.SQL: info16.8 Backend: Datenbank-Migration
Datei: backend/src/main/resources/db/migration/V1__init.sql
create table customers (
id uuid primary key,
name varchar(160) not null,
email varchar(200) not null unique,
status varchar(40) not null,
created_at timestamp with time zone not null
);
create table orders (
id uuid primary key,
customer_id uuid not null references customers(id),
title varchar(200) not null,
amount numeric(12, 2) not null,
status varchar(40) not null,
created_at timestamp with time zone not null
);
create index idx_orders_customer_id on orders(customer_id);
create index idx_orders_status on orders(status);16.9 Backend: Domain-Modell Customer
Datei: backend/src/main/java/com/example/portal/customer/CustomerStatus.java
package com.example.portal.customer;
public enum CustomerStatus {
ACTIVE,
INACTIVE
}Datei: backend/src/main/java/com/example/portal/customer/Customer.java
package com.example.portal.customer;
import jakarta.persistence.*;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "customers")
public class Customer {
@Id
private UUID id;
@Column(nullable = false, length = 160)
private String name;
@Column(nullable = false, unique = true, length = 200)
private String email;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 40)
private CustomerStatus status;
@Column(nullable = false)
private OffsetDateTime createdAt;
protected Customer() {
}
public Customer(String name, String email) {
this.id = UUID.randomUUID();
this.name = name;
this.email = email;
this.status = CustomerStatus.ACTIVE;
this.createdAt = OffsetDateTime.now();
}
public UUID getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public CustomerStatus getStatus() { return status; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public void deactivate() {
this.status = CustomerStatus.INACTIVE;
}
}16.10 Backend: DTOs
Datei: CustomerCreateRequest.java
package com.example.portal.customer.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record CustomerCreateRequest(
@NotBlank @Size(max = 160) String name,
@NotBlank @Email @Size(max = 200) String email
) {}Datei: CustomerResponse.java
package com.example.portal.customer.dto;
import com.example.portal.customer.CustomerStatus;
import java.time.OffsetDateTime;
import java.util.UUID;
public record CustomerResponse(
UUID id,
String name,
String email,
CustomerStatus status,
OffsetDateTime createdAt
) {}16.11 Backend: Repository, Service, Controller
Datei: CustomerRepository.java
package com.example.portal.customer;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface CustomerRepository extends JpaRepository<Customer, UUID> {
Optional<Customer> findByEmail(String email);
boolean existsByEmail(String email);
}Datei: CustomerService.java
package com.example.portal.customer;
import com.example.portal.customer.dto.CustomerCreateRequest;
import com.example.portal.customer.dto.CustomerResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
@Service
public class CustomerService {
private final CustomerRepository repository;
public CustomerService(CustomerRepository repository) {
this.repository = repository;
}
@Transactional
public CustomerResponse create(CustomerCreateRequest request) {
if (repository.existsByEmail(request.email())) {
throw new IllegalArgumentException("Email already exists");
}
Customer customer = new Customer(request.name(), request.email());
return toResponse(repository.save(customer));
}
@Transactional(readOnly = true)
public List<CustomerResponse> findAll() {
return repository.findAll().stream().map(this::toResponse).toList();
}
@Transactional(readOnly = true)
public CustomerResponse findById(UUID id) {
return repository.findById(id)
.map(this::toResponse)
.orElseThrow(() -> new IllegalArgumentException("Customer not found"));
}
private CustomerResponse toResponse(Customer customer) {
return new CustomerResponse(
customer.getId(),
customer.getName(),
customer.getEmail(),
customer.getStatus(),
customer.getCreatedAt()
);
}
}Datei: CustomerController.java
package com.example.portal.customer;
import com.example.portal.customer.dto.CustomerCreateRequest;
import com.example.portal.customer.dto.CustomerResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/customers")
public class CustomerController {
private final CustomerService service;
public CustomerController(CustomerService service) {
this.service = service;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CustomerResponse create(@Valid @RequestBody CustomerCreateRequest request) {
return service.create(request);
}
@GetMapping
public List<CustomerResponse> findAll() {
return service.findAll();
}
@GetMapping("/{id}")
public CustomerResponse findById(@PathVariable UUID id) {
return service.findById(id);
}
}16.12 Backend: Fehlerbehandlung
Datei: common/error/ApiError.java
package com.example.portal.common.error;
import java.time.OffsetDateTime;
public record ApiError(
OffsetDateTime timestamp,
int status,
String error,
String message,
String path
) {}Datei: common/error/GlobalExceptionHandler.java
package com.example.portal.common.error;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.time.OffsetDateTime;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ApiError handleIllegalArgument(IllegalArgumentException ex, HttpServletRequest request) {
return new ApiError(
OffsetDateTime.now(),
400,
"Bad Request",
ex.getMessage(),
request.getRequestURI()
);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ApiError handleValidation(MethodArgumentNotValidException ex, HttpServletRequest request) {
String message = ex.getBindingResult().getFieldErrors().stream()
.findFirst()
.map(error -> error.getField() + " " + error.getDefaultMessage())
.orElse("Validation failed");
return new ApiError(
OffsetDateTime.now(),
400,
"Validation Error",
message,
request.getRequestURI()
);
}
}16.13 Backend: Order-Modul als Agenten-Aufgabe
Implementiere das Order-Modul bewusst mit Codex oder Claude Code, statt alles manuell zu schreiben. Dadurch trainierst du agentisches Arbeiten.
Du bist Senior Java/Spring-Boot-Entwickler.
Implementiere ein Order-Modul passend zur bestehenden Customer-Struktur.
Fachliche Anforderungen:
- Entity Order mit id, customer, title, amount, status, createdAt
- Status: DRAFT, CONFIRMED, CANCELLED
- POST /api/orders
- GET /api/orders
- GET /api/orders/{id}
- GET /api/customers/{customerId}/orders
- OrderCreateRequest mit customerId, title, amount
- amount muss positiv sein
- title darf nicht leer sein
Technische Anforderungen:
- JPA Beziehung ManyToOne zu Customer
- Flyway Migration für orders ist bereits vorhanden, prüfe sie
- DTOs statt Entities in der API
- Service mit Transaktionen
- Controller Tests oder Integration Tests
- bestehende Fehlerbehandlung nutzen
Regeln:
- Erstelle zuerst einen Plan
- Ändere keine Architektur ohne Begründung
- Führe Tests aus
- Zeige am Ende geänderte Dateien und offene Risiken
16.14 Backend lokal testen
cd backend
docker compose up -d
./mvnw test
./mvnw spring-boot:runHealth Check:
curl http://localhost:8080/actuator/healthKunden anlegen:
curl -X POST http://localhost:8080/api/customers \
-H "Content-Type: application/json" \
-d '{"name":"Ada Lovelace","email":"ada@example.com"}'Kunden abrufen:
curl http://localhost:8080/api/customers16.15 React/Vite-Frontend: API Client
Datei: frontend-react/.env
VITE_API_BASE_URL=http://localhost:8080Datei: frontend-react/src/api/client.ts
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080";
export async function apiRequest<T>(path: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
headers: {
"Content-Type": "application/json",
...(options.headers ?? {}),
},
...options,
});
if (!response.ok) {
const errorBody = await response.json().catch(() => null);
throw new Error(errorBody?.message ?? `Request failed with ${response.status}`);
}
return response.json() as Promise<T>;
}Datei: frontend-react/src/api/customers.ts
import { apiRequest } from "./client";
export type CustomerStatus = "ACTIVE" | "INACTIVE";
export type Customer = {
id: string;
name: string;
email: string;
status: CustomerStatus;
createdAt: string;
};
export type CustomerCreateRequest = {
name: string;
email: string;
};
export function getCustomers() {
return apiRequest<Customer[]>("/api/customers");
}
export function createCustomer(data: CustomerCreateRequest) {
return apiRequest<Customer>("/api/customers", {
method: "POST",
body: JSON.stringify(data),
});
}16.16 React/Vite-Frontend: Customer List
Datei: frontend-react/src/features/customers/CustomerList.tsx
import { useEffect, useState } from "react";
import { Customer, getCustomers } from "../../api/customers";
export function CustomerList() {
const [customers, setCustomers] = useState<Customer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
getCustomers()
.then(setCustomers)
.catch((err: unknown) => setError(err instanceof Error ? err.message : "Unknown error"))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Lade Kunden...</p>;
if (error) return <p role="alert">Fehler: {error}</p>;
if (customers.length === 0) return <p>Keine Kunden vorhanden.</p>;
return (
<section>
<h2>Kunden</h2>
<ul>
{customers.map((customer) => (
<li key={customer.id}>
<strong>{customer.name}</strong> — {customer.email} — {customer.status}
</li>
))}
</ul>
</section>
);
}
Datei: frontend-react/src/App.tsx
import { CustomerList } from "./features/customers/CustomerList";
export default function App() {
return (
<main style={{ maxWidth: 960, margin: "40px auto", fontFamily: "system-ui" }}>
<h1>Customer Order Portal</h1>
<CustomerList />
</main>
);
}
Start:
cd frontend-react
npm run dev16.17 Angular-Alternative: API Service
Falls du Angular statt React nutzen willst, starte so:
cd frontend-angular
ng generate interface core/api/customer
ng generate service core/api/customers
ng generate component features/customers/customer-listDatei: frontend-angular/src/app/core/api/customer.ts
export type CustomerStatus = 'ACTIVE' | 'INACTIVE';
export interface Customer {
id: string;
name: string;
email: string;
status: CustomerStatus;
createdAt: string;
}
export interface CustomerCreateRequest {
name: string;
email: string;
}Datei: frontend-angular/src/app/core/api/customers.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Customer, CustomerCreateRequest } from './customer';
@Injectable({ providedIn: 'root' })
export class CustomersService {
private readonly baseUrl = 'http://localhost:8080/api/customers';
constructor(private readonly http: HttpClient) {}
getCustomers(): Observable<Customer[]> {
return this.http.get<Customer[]>(this.baseUrl);
}
createCustomer(request: CustomerCreateRequest): Observable<Customer> {
return this.http.post<Customer>(this.baseUrl, request);
}
}16.18 CORS für lokale Entwicklung
Für lokale Entwicklung kann das Frontend auf Port 5173 oder 4200 laufen, während das Backend auf 8080 läuft. Ergänze im Backend eine kontrollierte CORS-Konfiguration.
Datei: backend/src/main/java/com/example/portal/common/WebConfig.java
package com.example.portal.common;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173", "http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*");
}
}Für Produktion solltest du CORS nicht pauschal öffnen. Erlaube nur konkrete Domains deiner Anwendung.
16.19 Test-Beispiel für Backend
Datei: backend/src/test/java/com/example/portal/customer/CustomerControllerTest.java
package com.example.portal.customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class CustomerControllerTest {
@Autowired
MockMvc mockMvc;
@Test
void createsCustomer() throws Exception {
mockMvc.perform(post("/api/customers")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name":"Grace Hopper","email":"grace@example.com"}
"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Grace Hopper"))
.andExpect(jsonPath("$.email").value("grace@example.com"));
}
}16.20 README Vorlage
Datei: _intern/sources/_intern\sources\README.md
# Customer Order Portal
Fullstack-Beispielprojekt mit Java/Spring Boot Backend und React/Vite oder Angular Frontend.
## Voraussetzungen
- Java 21 oder 25
- Maven Wrapper
- Node.js LTS
- Docker oder Podman
## Backend starten
```bash
cd backend
docker compose up -d
./mvnw spring-boot:run
```
Backend läuft auf: http://localhost:8080
## React Frontend starten
```bash
cd frontend-react
npm install
npm run dev
```
React läuft auf: http://localhost:5173
## Angular Frontend starten
```bash
cd frontend-angular
npm install
ng serve
```
Angular läuft auf: http://localhost:4200
## Wichtige Endpunkte
- GET /actuator/health
- POST /api/customers
- GET /api/customers
- GET /api/customers/{id}
- POST /api/orders
- GET /api/orders
## Tests
```bash
cd backend
./mvnw test
```16.21 Architekturdiagramm als Mermaid
Datei: docs/architecture.md
flowchart LR
Browser[Browser]
React[React/Vite Frontend]
Angular[Angular Frontend]
API[Spring Boot REST API]
Service[Service Layer]
Repo[Spring Data JPA]
DB[(PostgreSQL)]
Browser --> React
Browser --> Angular
React --> API
Angular --> API
API --> Service
Service --> Repo
Repo --> DB
16.22 Codex/Claude Code Aufgabenplan für dieses Projekt
Nutze diese Reihenfolge, damit der Agent nicht zu viel auf einmal verändert.
Aufgabe 1: Projektanalyse
Analysiere dieses Repository.
Erkläre Struktur, Startbefehle, Datenfluss, Risiken und fehlende Teile.
Ändere keine Dateien.
Aufgabe 2: Backend Order-Modul
Implementiere das Order-Modul gemäß docs/api.md.
Erstelle zuerst einen Plan.
Danach implementiere Entity, Repository, Service, Controller, DTOs, Migration und Tests.
Führe ./mvnw test aus.
Aufgabe 3: Frontend Create Form
Ergänze im React-Frontend ein CustomerCreateForm.
Anforderungen:
- name und email Eingaben
- clientseitige einfache Validierung
- POST /api/customers nutzen
- nach Erfolg Liste aktualisieren
- Loading und Error State anzeigen
- keine externe Form-Library hinzufügen
Aufgabe 4: Refactoring
Prüfe das Backend auf Duplikate, schlechte Fehlerbehandlung und fehlende Tests.
Gib zuerst eine priorisierte Liste.
Ändere erst nach meiner Freigabe Dateien.
Aufgabe 5: Dokumentation
Aktualisiere _intern/sources/_intern\sources\README.md und docs/architecture.md.
Beschreibe Startbefehle, API-Endpunkte, Architektur, Tests und bekannte Einschränkungen.
Keine Implementierungsdateien ändern.
16.23 Abnahmekriterien
Das Beispielprojekt gilt als fertig, wenn diese Punkte erfüllt sind:
16.24 Erweiterungen für echte Enterprise-Projekte
Wenn die Basis läuft, erweitere in dieser Reihenfolge:
- OpenAPI: API dokumentieren und Frontend-Typen generieren.
- Security: Spring Security, JWT/OAuth2 oder Keycloak integrieren.
- Pagination: Listen-Endpunkte nicht unbegrenzt liefern.
- Testcontainers: echte PostgreSQL-Integrationstests statt Mock-Datenbank.
- CI: GitHub Actions oder GitLab CI für Build, Test und Lint.
- Observability: strukturierte Logs, Metrics, Tracing.
- Docker Images: Backend und Frontend containerisieren.
- Deployment: zuerst Docker Compose, später Kubernetes oder Cloud Runtime.
17. Beispielprojekt: Erweiterter HTML Manager
Dieses Beispiel ist ein ausgebauter HTML-/Content-Manager für interne Enterprise-Anwendungen. Die Idee: Fachbereiche können HTML-Bausteine, Landingpages, E-Mail-Templates oder Dokumentationsseiten pflegen, versionieren, prüfen und veröffentlichen, ohne dass jedes Mal ein Deployment nötig ist.
Der HTML Manager ist absichtlich praxisnah, weil er typische Enterprise-Themen kombiniert: Rollen, Freigabeprozess, Versionierung, Preview, XSS-Sicherheit, Audit-Log, Validierung, API, Frontend-Editor und Deployment.
17.1 Ziel des Projekts
Erklärung: Warum ein HTML Manager anspruchsvoller ist als er aussieht
Ein HTML Manager wirkt auf den ersten Blick wie ein einfacher Editor. In Wirklichkeit berührt er viele Enterprise-Themen: Rollen, Versionierung, Freigaben, sichere Vorschau, XSS-Schutz, Änderungsverlauf und Publishing. Besonders wichtig ist, dass gespeicherter HTML-Code niemals ungeprüft im Admin-Bereich oder auf öffentlichen Seiten ausgegeben wird. Sobald Benutzer HTML speichern dürfen, musst du klar definieren, welche Tags und Attribute erlaubt sind.
Das Projekt trainiert deshalb eine andere Art von Denken als ein normales CRUD-Modul. Du speicherst nicht nur Daten, sondern potenziell ausführbaren Inhalt. Genau deshalb gehören Sanitizing, Preview-Sandbox, Rechteprüfung und Audit-Log zum Kern des Designs.
| Risiko | Erklärung | Schutzmaßnahme |
|---|---|---|
| XSS | Benutzer speichert schädliches Script im HTML | Allowlist, Sanitizing, CSP, Sandbox-Iframe |
| Falsche Veröffentlichung | Entwurf geht ohne Review live | Statusmodell Draft → Review → Published |
| Unklare Änderungen | Niemand weiß, wer was geändert hat | Versionierung und Audit-Log |
| Rechte-Missbrauch | Redakteur ändert Systembereiche | Rollen und Berechtigungen pro Aktion |
| Kaputte Seiten | Ungültiges HTML beschädigt Layout | Preview, Validierung, Rollback |
Merksatz: Content-Management ist nicht nur Editor-UI. Es ist ein kontrollierter Veröffentlichungsprozess.
| Bereich | Beschreibung |
|---|---|
| Fachlicher Zweck | HTML-Seiten und Templates erstellen, bearbeiten, prüfen und veröffentlichen |
| Backend | Spring Boot, REST API, PostgreSQL, Flyway, Validation, Security, Audit |
| Frontend | React/Vite oder Angular, Editor, Live Preview, Version History, Publish Flow |
| AI-Agenten-Fokus | Codex/Claude Code erstellen Module, Tests, Refactorings und Security Checks |
17.2 Rollen und Berechtigungen
| Rolle | Rechte |
|---|---|
AUTHOR |
Entwürfe erstellen und bearbeiten |
REVIEWER |
Entwürfe prüfen, Kommentare schreiben, ablehnen |
PUBLISHER |
geprüfte Versionen veröffentlichen |
ADMIN |
Kategorien, Rollen und globale Einstellungen verwalten |
17.3 Hauptfunktionen
- HTML-Seite erstellen.
- Slug, Titel, Sprache, Kategorie und Status verwalten.
- HTML-Inhalt speichern und versionieren.
- Live Preview in sicherem Sandbox-Iframe anzeigen.
- HTML serverseitig bereinigen oder blockieren.
- Statusfluss:
DRAFT -> IN_REVIEW -> APPROVED -> PUBLISHED. - Audit-Log für Änderungen, Freigaben und Veröffentlichungen.
- Suche nach Titel, Slug, Kategorie und Status.
- Rollback auf ältere Version.
- Export als HTML-Datei oder JSON.
17.4 Empfohlener Stack
| Schicht | Option A | Option B |
|---|---|---|
| Backend | Spring Boot REST | Quarkus REST |
| Datenbank | PostgreSQL | MariaDB |
| Migration | Flyway | Liquibase |
| Frontend | React + Vite | Angular |
| Editor | <textarea> für Start |
Monaco Editor später |
| Preview | Sandbox-Iframe | getrennte Preview-Route |
| Sicherheit | Spring Security + Rollen | Gateway + JWT |
| Tests | JUnit, Testcontainers, Playwright | JUnit, Cypress |
17.5 Projektstruktur
html-manager/
├─ backend/
│ ├─ src/main/java/com/example/htmlmanager/
│ │ ├─ HtmlManagerApplication.java
│ │ ├─ pages/
│ │ │ ├─ HtmlPage.java
│ │ │ ├─ HtmlPageStatus.java
│ │ │ ├─ HtmlPageRepository.java
│ │ │ ├─ HtmlPageService.java
│ │ │ ├─ HtmlPageController.java
│ │ │ ├─ dto/
│ │ │ │ ├─ CreateHtmlPageRequest.java
│ │ │ │ ├─ UpdateHtmlPageRequest.java
│ │ │ │ └─ HtmlPageResponse.java
│ │ ├─ versions/
│ │ │ ├─ HtmlPageVersion.java
│ │ │ └─ HtmlPageVersionRepository.java
│ │ ├─ audit/
│ │ │ ├─ AuditEvent.java
│ │ │ └─ AuditService.java
│ │ └─ security/
│ │ └─ SecurityConfig.java
│ └─ src/main/resources/db/migration/
│ ├─ V1__create_html_pages.sql
│ └─ V2__create_html_page_versions.sql
├─ frontend-react/
│ ├─ src/api/htmlPagesApi.ts
│ ├─ src/features/html-pages/HtmlPageList.tsx
│ ├─ src/features/html-pages/HtmlPageEditor.tsx
│ ├─ src/features/html-pages/HtmlPreview.tsx
│ └─ src/features/html-pages/VersionHistory.tsx
└─ docker-compose.yml
17.6 Datenmodell
CREATE TABLE html_pages (
id UUID PRIMARY KEY,
slug VARCHAR(160) NOT NULL UNIQUE,
title VARCHAR(240) NOT NULL,
category VARCHAR(120) NOT NULL,
language VARCHAR(12) NOT NULL DEFAULT 'de',
status VARCHAR(40) NOT NULL,
current_version INTEGER NOT NULL DEFAULT 1,
created_by VARCHAR(160) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE html_page_versions (
id UUID PRIMARY KEY,
page_id UUID NOT NULL REFERENCES html_pages(id),
version INTEGER NOT NULL,
html_content TEXT NOT NULL,
plain_text_summary TEXT,
change_note VARCHAR(500),
created_by VARCHAR(160) NOT NULL,
created_at TIMESTAMP NOT NULL,
UNIQUE(page_id, version)
);
CREATE TABLE html_page_audit_events (
id UUID PRIMARY KEY,
page_id UUID NOT NULL REFERENCES html_pages(id),
action VARCHAR(80) NOT NULL,
actor VARCHAR(160) NOT NULL,
message VARCHAR(800),
created_at TIMESTAMP NOT NULL
);17.7 Backend: Status-Enum
package com.example.htmlmanager.pages;
public enum HtmlPageStatus {
DRAFT,
IN_REVIEW,
APPROVED,
PUBLISHED,
ARCHIVED
}17.8 Backend: Entity
package com.example.htmlmanager.pages;
import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "html_pages")
public class HtmlPage {
@Id
private UUID id;
@Column(nullable = false, unique = true, length = 160)
private String slug;
@Column(nullable = false, length = 240)
private String title;
@Column(nullable = false, length = 120)
private String category;
@Column(nullable = false, length = 12)
private String language;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 40)
private HtmlPageStatus status;
@Column(nullable = false)
private Integer currentVersion;
@Column(nullable = false, length = 160)
private String createdBy;
@Column(nullable = false)
private Instant createdAt;
@Column(nullable = false)
private Instant updatedAt;
protected HtmlPage() {}
public HtmlPage(String slug, String title, String category, String language, String createdBy) {
this.id = UUID.randomUUID();
this.slug = slug;
this.title = title;
this.category = category;
this.language = language;
this.status = HtmlPageStatus.DRAFT;
this.currentVersion = 1;
this.createdBy = createdBy;
this.createdAt = Instant.now();
this.updatedAt = Instant.now();
}
public void submitForReview() {
if (status != HtmlPageStatus.DRAFT) {
throw new IllegalStateException("Nur Entwürfe können zur Prüfung eingereicht werden.");
}
status = HtmlPageStatus.IN_REVIEW;
updatedAt = Instant.now();
}
public void approve() {
if (status != HtmlPageStatus.IN_REVIEW) {
throw new IllegalStateException("Nur geprüfte Entwürfe können freigegeben werden.");
}
status = HtmlPageStatus.APPROVED;
updatedAt = Instant.now();
}
public void publish() {
if (status != HtmlPageStatus.APPROVED) {
throw new IllegalStateException("Nur freigegebene Seiten können veröffentlicht werden.");
}
status = HtmlPageStatus.PUBLISHED;
updatedAt = Instant.now();
}
public UUID getId() { return id; }
public String getSlug() { return slug; }
public String getTitle() { return title; }
public String getCategory() { return category; }
public String getLanguage() { return language; }
public HtmlPageStatus getStatus() { return status; }
public Integer getCurrentVersion() { return currentVersion; }
public Instant getCreatedAt() { return createdAt; }
public Instant getUpdatedAt() { return updatedAt; }
}17.9 Backend: DTOs
package com.example.htmlmanager.pages.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public record CreateHtmlPageRequest(
@NotBlank @Pattern(regexp = "^[a-z0-9-]+$") @Size(max = 160)
String slug,
@NotBlank @Size(max = 240)
String title,
@NotBlank @Size(max = 120)
String category,
@NotBlank @Size(max = 12)
String language,
@NotBlank @Size(max = 200_000)
String htmlContent,
@Size(max = 500)
String changeNote
) {}package com.example.htmlmanager.pages.dto;
import java.time.Instant;
import java.util.UUID;
public record HtmlPageResponse(
UUID id,
String slug,
String title,
String category,
String language,
String status,
Integer currentVersion,
Instant updatedAt
) {}17.10 Backend: Repository
package com.example.htmlmanager.pages;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface HtmlPageRepository extends JpaRepository<HtmlPage, UUID> {
Optional<HtmlPage> findBySlug(String slug);
Page<HtmlPage> findByTitleContainingIgnoreCaseOrSlugContainingIgnoreCase(
String title,
String slug,
Pageable pageable
);
}17.11 Backend: Service mit Sicherheitsgrenzen
Für den Start kann der Service HTML zunächst nur grob blockieren. In echten Projekten brauchst du eine geprüfte HTML-Sanitizing-Strategie, serverseitige Validierung, CSP und Security Review.
package com.example.htmlmanager.pages;
import com.example.htmlmanager.pages.dto.CreateHtmlPageRequest;
import com.example.htmlmanager.pages.dto.HtmlPageResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class HtmlPageService {
private final HtmlPageRepository repository;
public HtmlPageService(HtmlPageRepository repository) {
this.repository = repository;
}
@Transactional
public HtmlPageResponse create(CreateHtmlPageRequest request, String actor) {
assertHtmlIsAcceptable(request.htmlContent());
repository.findBySlug(request.slug()).ifPresent(existing -> {
throw new IllegalArgumentException("Slug ist bereits vergeben: " + request.slug());
});
HtmlPage page = new HtmlPage(
request.slug(),
request.title(),
request.category(),
request.language(),
actor
);
HtmlPage saved = repository.save(page);
return toResponse(saved);
}
@Transactional
public HtmlPageResponse submitForReview(java.util.UUID id) {
HtmlPage page = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("HTML-Seite nicht gefunden"));
page.submitForReview();
return toResponse(page);
}
private void assertHtmlIsAcceptable(String html) {
String lower = html.toLowerCase();
if (lower.contains("<script") || lower.contains("javascript:")) {
throw new IllegalArgumentException("Unsicheres HTML wurde blockiert.");
}
}
private HtmlPageResponse toResponse(HtmlPage page) {
return new HtmlPageResponse(
page.getId(),
page.getSlug(),
page.getTitle(),
page.getCategory(),
page.getLanguage(),
page.getStatus().name(),
page.getCurrentVersion(),
page.getUpdatedAt()
);
}
}17.12 Backend: REST Controller
package com.example.htmlmanager.pages;
import com.example.htmlmanager.pages.dto.CreateHtmlPageRequest;
import com.example.htmlmanager.pages.dto.HtmlPageResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.UUID;
@RestController
@RequestMapping("/api/html-pages")
public class HtmlPageController {
private final HtmlPageService service;
public HtmlPageController(HtmlPageService service) {
this.service = service;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public HtmlPageResponse create(
@Valid @RequestBody CreateHtmlPageRequest request,
Principal principal
) {
String actor = principal != null ? principal.getName() : "local-dev";
return service.create(request, actor);
}
@PostMapping("/{id}/submit")
public HtmlPageResponse submit(@PathVariable UUID id) {
return service.submitForReview(id);
}
}17.13 Frontend: API Client
// src/api/htmlPagesApi.ts
export type HtmlPage = {
id: string;
slug: string;
title: string;
category: string;
language: string;
status: string;
currentVersion: number;
updatedAt: string;
};
export type CreateHtmlPageRequest = {
slug: string;
title: string;
category: string;
language: string;
htmlContent: string;
changeNote?: string;
};
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080";
export async function createHtmlPage(payload: CreateHtmlPageRequest): Promise<HtmlPage> {
const response = await fetch(`${API_BASE}/api/html-pages`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTML-Seite konnte nicht erstellt werden: ${response.status}`);
}
return response.json();
}17.14 Frontend: Editor mit Preview
// src/features/html-pages/HtmlPageEditor.tsx
import { useMemo, useState } from "react";
import { createHtmlPage } from "../../api/htmlPagesApi";
import { HtmlPreview } from "./HtmlPreview";
export function HtmlPageEditor() {
const [title, setTitle] = useState("Neue Landingpage");
const [slug, setSlug] = useState("neue-landingpage");
const [category, setCategory] = useState("Marketing");
const [htmlContent, setHtmlContent] = useState("<h1>Hallo Enterprise</h1><p>Startinhalt</p>");
const [message, setMessage] = useState<string | null>(null);
const canSave = useMemo(() => title.trim() && slug.trim() && htmlContent.trim(), [title, slug, htmlContent]);
async function handleSave() {
setMessage(null);
const created = await createHtmlPage({
title,
slug,
category,
language: "de",
htmlContent,
changeNote: "Initiale Version",
});
setMessage(`Gespeichert: ${created.slug}`);
}
return (
<main className="editor-layout">
<section className="editor-panel">
<h1>HTML Manager</h1>
<label>
Titel
<input value={title} onChange={(event) => setTitle(event.target.value)} />
</label>
<label>
Slug
<input value={slug} onChange={(event) => setSlug(event.target.value)} />
</label>
<label>
Kategorie
<input value={category} onChange={(event) => setCategory(event.target.value)} />
</label>
<label>
HTML Inhalt
<textarea
rows={18}
value={htmlContent}
onChange={(event) => setHtmlContent(event.target.value)}
/>
</label>
<button disabled={!canSave} onClick={handleSave}>Speichern</button>
{message && <p role="status">{message}</p>}
</section>
<section className="preview-panel">
<h2>Live Preview</h2>
<HtmlPreview html={htmlContent} />
</section>
</main>
);
}
17.15 Frontend: sichere Preview im Sandbox-Iframe
// src/features/html-pages/HtmlPreview.tsx
export function HtmlPreview({ html }: { html: string }) {
const document = `
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body { font-family: system-ui, sans-serif; padding: 24px; line-height: 1.6; }
img { max-width: 100%; height: auto; }
</style>
</head>
<body>${html}</body>
</html>`;
return (
<iframe
title="HTML Preview"
sandbox=""
srcDoc={document}
style={{ width: "100%", minHeight: 520, border: "1px solid #ddd", borderRadius: 12 }}
/>
);
}
17.16 CSS für das Editor-Layout
.editor-layout {
display: grid;
grid-template-columns: minmax(360px, 520px) 1fr;
gap: 24px;
padding: 24px;
}
.editor-panel,
.preview-panel {
border: 1px solid #e5e7eb;
border-radius: 16px;
padding: 20px;
background: #fff;
}
label {
display: grid;
gap: 6px;
margin-bottom: 14px;
font-weight: 700;
}
input,
textarea {
width: 100%;
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 10px 12px;
font: inherit;
}
textarea {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
button {
border: 0;
border-radius: 999px;
padding: 10px 16px;
font-weight: 800;
cursor: pointer;
}
@media (max-width: 960px) {
.editor-layout {
grid-template-columns: 1fr;
}
}17.17 Angular-Alternative: Feature-Struktur
src/app/features/html-pages/
├─ html-pages.routes.ts
├─ data-access/
│ └─ html-pages-api.service.ts
├─ ui/
│ ├─ html-preview.component.ts
│ └─ status-badge.component.ts
└─ pages/
├─ html-page-list.component.ts
└─ html-page-editor.component.ts
// src/app/features/html-pages/data-access/html-pages-api.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
export type HtmlPage = {
id: string;
slug: string;
title: string;
category: string;
language: string;
status: string;
currentVersion: number;
updatedAt: string;
};
@Injectable({ providedIn: "root" })
export class HtmlPagesApiService {
private readonly baseUrl = "http://localhost:8080/api/html-pages";
constructor(private readonly http: HttpClient) {}
create(payload: unknown) {
return this.http.post<HtmlPage>(this.baseUrl, payload);
}
submit(id: string) {
return this.http.post<HtmlPage>(`${this.baseUrl}/${id}/submit`, {});
}
}17.18 AI-Aufgaben für Codex oder Claude Code
Analysiere das HTML-Manager-Projekt.
Erstelle einen Plan für ein Versionierungsmodul.
Anforderungen:
- Jede Änderung am HTML-Inhalt erzeugt eine neue Version.
- Versionen sind unveränderlich.
- Es gibt eine Endpoint-Liste für alle Versionen einer Seite.
- Es gibt einen Rollback-Endpunkt.
- Tests mit Service- und Controller-Ebene.
Ändere noch keine Dateien, bevor du den Plan erklärt hast.
Implementiere im Frontend eine Version-History-Seitenleiste.
Regeln:
- React + TypeScript verwenden.
- Ladezustand, Fehlerzustand und Empty State darstellen.
- Beim Klick auf eine Version wird die Preview aktualisiert.
- Keine neue UI Library einführen.
- Danach kurze Diff-Zusammenfassung geben.
Führe ein Security Review für den HTML Manager durch.
Fokus:
- XSS-Risiken
- Preview-Sandbox
- serverseitige Sanitization
- Content Security Policy
- Rollenmodell
- Audit-Log
Erstelle Findings mit Schweregrad und konkretem Fix.
Ändere noch keine Dateien.
17.19 Abnahmekriterien
17.20 Erweiterungen
| Erweiterung | Nutzen |
|---|---|
| Asset Manager | Bilder, CSS-Dateien und Downloads zentral verwalten |
| Template Variablen | Platzhalter wie {{customerName}} oder {{date}} einsetzen |
| Mehrsprachigkeit | Pro Seite mehrere Sprachversionen pflegen |
| Preview-URLs | Review-Link für Fachbereich generieren |
| Scheduled Publishing | Veröffentlichung zu bestimmtem Zeitpunkt planen |
| Export | Statische HTML-Dateien für CDN oder Intranet erzeugen |
| Rechte pro Kategorie | Marketing, Legal, HR getrennt verwalten |
| AI Review | Agent prüft Barrierefreiheit, Tonalität und defekte Links |
18. Beispielprojekt: Ticket & Approval Workflow
Dieses zweite Beispiel ist ein internes Service-Desk- und Freigabeportal. Es passt sehr gut zu Enterprise-Teams, weil fast jede Organisation ähnliche Abläufe hat: Anfrage erstellen, zuständige Person finden, Prüfung durchführen, genehmigen oder ablehnen, Status verfolgen und alles auditierbar dokumentieren.
18.1 Ziel des Projekts
Erklärung: Warum Workflows in Enterprise-Projekten so wichtig sind
Viele Unternehmensprozesse sind keine einfachen „Speichern und fertig“-Abläufe. Ein Ticket, Antrag oder Freigabeprozess durchläuft mehrere Zustände. Genau hier entstehen häufig Fehler: Benutzer überspringen Schritte, Zustände werden falsch gesetzt, Berechtigungen sind unklar oder niemand kann später nachvollziehen, warum eine Entscheidung getroffen wurde.
Das Ticket-&-Approval-Projekt zeigt dir, wie man solche Prozesse sauber modelliert. Der wichtigste Teil ist nicht das Formular, sondern die Statusmaschine. Sie definiert, welche Übergänge erlaubt sind: Ein neues Ticket darf zum Beispiel von OPEN nach IN_REVIEW, aber nicht direkt nach APPROVED, wenn ein Review notwendig ist. Diese Regeln gehören in die Fachlogik, nicht nur in die UI. Die UI darf Buttons ausblenden, aber das Backend muss die Regel wirklich erzwingen.
| Konzept | Erklärung |
|---|---|
| Statusmaschine | Legt fest, welche Zustandswechsel erlaubt sind. |
| Audit-Log | Dokumentiert, wer wann welche Entscheidung getroffen hat. |
| Rollen | Bestimmt, wer erstellen, prüfen, genehmigen oder ablehnen darf. |
| Kommentare | Erklären Entscheidungen und Rückfragen. |
| Benachrichtigungen | Informieren Beteiligte über notwendige Aktionen. |
Praxisregel: Alles, was fachlich wichtig ist, muss im Backend geschützt werden. Das Frontend ist nur die Bedienoberfläche, nicht die Sicherheitsgrenze.
| Bereich | Beschreibung |
|---|---|
| Fachlicher Zweck | Tickets, Anfragen und Freigaben verwalten |
| Backend | Spring Boot, PostgreSQL, REST, Security, Workflow-Service |
| Frontend | Angular oder React mit Dashboard, Listen, Detailseite und Formularen |
| Enterprise-Fokus | Rollen, Statusmaschine, Audit, SLA, Kommentare, Anhänge |
| AI-Agenten-Fokus | Workflow-Regeln, Tests, UI-Komponenten, Refactoring |
18.2 Beispiel-Use-Cases
- Mitarbeiter erstellt eine Anfrage, zum Beispiel „VPN-Zugang“, „Software-Freigabe“ oder „Budget-Antrag“.
- System weist Anfrage einer Kategorie und einem Team zu.
- Reviewer prüft die Anfrage.
- Approver genehmigt oder lehnt ab.
- Antragsteller sieht Status und Kommentare.
- Audit-Log zeigt alle Statuswechsel.
- SLA-Anzeige markiert verspätete Tickets.
18.3 Statusmodell
NEW
-> TRIAGED
-> IN_PROGRESS
-> WAITING_FOR_APPROVAL
-> APPROVED
-> DONE
Alternative Pfade:
NEW -> REJECTED
WAITING_FOR_APPROVAL -> REJECTED
IN_PROGRESS -> CANCELLED
18.4 Projektstruktur
ticket-approval/
├─ backend/
│ ├─ src/main/java/com/example/workflow/
│ │ ├─ WorkflowApplication.java
│ │ ├─ tickets/
│ │ │ ├─ Ticket.java
│ │ │ ├─ TicketStatus.java
│ │ │ ├─ TicketPriority.java
│ │ │ ├─ TicketRepository.java
│ │ │ ├─ TicketService.java
│ │ │ ├─ TicketController.java
│ │ │ └─ dto/
│ │ ├─ comments/
│ │ ├─ approvals/
│ │ ├─ audit/
│ │ └─ security/
│ ├─ src/main/resources/db/migration/
│ └─ src/test/java/com/example/workflow/
├─ frontend-angular/
│ ├─ src/app/features/tickets/
│ ├─ src/app/features/dashboard/
│ └─ src/app/shared/
└─ docker-compose.yml
18.5 Datenbank-Migration
CREATE TABLE tickets (
id UUID PRIMARY KEY,
title VARCHAR(240) NOT NULL,
description TEXT NOT NULL,
category VARCHAR(120) NOT NULL,
priority VARCHAR(40) NOT NULL,
status VARCHAR(60) NOT NULL,
requester VARCHAR(160) NOT NULL,
assignee VARCHAR(160),
due_at TIMESTAMP,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE ticket_comments (
id UUID PRIMARY KEY,
ticket_id UUID NOT NULL REFERENCES tickets(id),
author VARCHAR(160) NOT NULL,
body TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
);
CREATE TABLE approval_steps (
id UUID PRIMARY KEY,
ticket_id UUID NOT NULL REFERENCES tickets(id),
approver VARCHAR(160) NOT NULL,
decision VARCHAR(60),
decision_note VARCHAR(800),
decided_at TIMESTAMP
);
CREATE TABLE ticket_audit_events (
id UUID PRIMARY KEY,
ticket_id UUID NOT NULL REFERENCES tickets(id),
actor VARCHAR(160) NOT NULL,
action VARCHAR(80) NOT NULL,
old_status VARCHAR(60),
new_status VARCHAR(60),
created_at TIMESTAMP NOT NULL
);18.6 Backend: Status und Priorität
package com.example.workflow.tickets;
public enum TicketStatus {
NEW,
TRIAGED,
IN_PROGRESS,
WAITING_FOR_APPROVAL,
APPROVED,
REJECTED,
DONE,
CANCELLED
}package com.example.workflow.tickets;
public enum TicketPriority {
LOW,
NORMAL,
HIGH,
CRITICAL
}18.7 Backend: Entity mit Statusübergängen
package com.example.workflow.tickets;
import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "tickets")
public class Ticket {
@Id
private UUID id;
@Column(nullable = false, length = 240)
private String title;
@Column(nullable = false)
private String description;
@Column(nullable = false, length = 120)
private String category;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 40)
private TicketPriority priority;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 60)
private TicketStatus status;
@Column(nullable = false, length = 160)
private String requester;
@Column(length = 160)
private String assignee;
private Instant dueAt;
@Column(nullable = false)
private Instant createdAt;
@Column(nullable = false)
private Instant updatedAt;
protected Ticket() {}
public Ticket(String title, String description, String category, TicketPriority priority, String requester) {
this.id = UUID.randomUUID();
this.title = title;
this.description = description;
this.category = category;
this.priority = priority;
this.requester = requester;
this.status = TicketStatus.NEW;
this.createdAt = Instant.now();
this.updatedAt = Instant.now();
}
public void triage(String assignee) {
requireStatus(TicketStatus.NEW);
this.assignee = assignee;
this.status = TicketStatus.TRIAGED;
this.updatedAt = Instant.now();
}
public void startWork() {
requireStatus(TicketStatus.TRIAGED);
this.status = TicketStatus.IN_PROGRESS;
this.updatedAt = Instant.now();
}
public void requestApproval() {
requireStatus(TicketStatus.IN_PROGRESS);
this.status = TicketStatus.WAITING_FOR_APPROVAL;
this.updatedAt = Instant.now();
}
public void approve() {
requireStatus(TicketStatus.WAITING_FOR_APPROVAL);
this.status = TicketStatus.APPROVED;
this.updatedAt = Instant.now();
}
public void reject() {
if (status != TicketStatus.NEW && status != TicketStatus.WAITING_FOR_APPROVAL) {
throw new IllegalStateException("Ticket kann in diesem Status nicht abgelehnt werden: " + status);
}
this.status = TicketStatus.REJECTED;
this.updatedAt = Instant.now();
}
private void requireStatus(TicketStatus expected) {
if (status != expected) {
throw new IllegalStateException("Erwarteter Status: " + expected + ", aktueller Status: " + status);
}
}
public UUID getId() { return id; }
public String getTitle() { return title; }
public TicketStatus getStatus() { return status; }
public TicketPriority getPriority() { return priority; }
public String getRequester() { return requester; }
public String getAssignee() { return assignee; }
public Instant getUpdatedAt() { return updatedAt; }
}18.8 Backend: Create Request und Response
package com.example.workflow.tickets.dto;
import com.example.workflow.tickets.TicketPriority;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public record CreateTicketRequest(
@NotBlank @Size(max = 240)
String title,
@NotBlank @Size(max = 5_000)
String description,
@NotBlank @Size(max = 120)
String category,
@NotNull
TicketPriority priority
) {}package com.example.workflow.tickets.dto;
import java.time.Instant;
import java.util.UUID;
public record TicketResponse(
UUID id,
String title,
String status,
String priority,
String requester,
String assignee,
Instant updatedAt
) {}18.9 Backend: Service
package com.example.workflow.tickets;
import com.example.workflow.tickets.dto.CreateTicketRequest;
import com.example.workflow.tickets.dto.TicketResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Service
public class TicketService {
private final TicketRepository repository;
public TicketService(TicketRepository repository) {
this.repository = repository;
}
@Transactional
public TicketResponse create(CreateTicketRequest request, String requester) {
Ticket ticket = new Ticket(
request.title(),
request.description(),
request.category(),
request.priority(),
requester
);
return toResponse(repository.save(ticket));
}
@Transactional
public TicketResponse triage(UUID id, String assignee) {
Ticket ticket = findTicket(id);
ticket.triage(assignee);
return toResponse(ticket);
}
@Transactional
public TicketResponse approve(UUID id) {
Ticket ticket = findTicket(id);
ticket.approve();
return toResponse(ticket);
}
private Ticket findTicket(UUID id) {
return repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Ticket nicht gefunden"));
}
private TicketResponse toResponse(Ticket ticket) {
return new TicketResponse(
ticket.getId(),
ticket.getTitle(),
ticket.getStatus().name(),
ticket.getPriority().name(),
ticket.getRequester(),
ticket.getAssignee(),
ticket.getUpdatedAt()
);
}
}18.10 Backend: Controller
package com.example.workflow.tickets;
import com.example.workflow.tickets.dto.CreateTicketRequest;
import com.example.workflow.tickets.dto.TicketResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.UUID;
@RestController
@RequestMapping("/api/tickets")
public class TicketController {
private final TicketService service;
public TicketController(TicketService service) {
this.service = service;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public TicketResponse create(@Valid @RequestBody CreateTicketRequest request, Principal principal) {
String requester = principal != null ? principal.getName() : "local-user";
return service.create(request, requester);
}
@PostMapping("/{id}/triage")
public TicketResponse triage(@PathVariable UUID id, @RequestParam String assignee) {
return service.triage(id, assignee);
}
@PostMapping("/{id}/approve")
public TicketResponse approve(@PathVariable UUID id) {
return service.approve(id);
}
}18.11 Frontend: Angular Dashboard-Idee
features/tickets/
├─ pages/
│ ├─ ticket-list-page.component.ts
│ ├─ ticket-detail-page.component.ts
│ └─ create-ticket-page.component.ts
├─ ui/
│ ├─ ticket-status-badge.component.ts
│ ├─ ticket-priority-badge.component.ts
│ └─ ticket-kanban-board.component.ts
└─ data-access/
└─ tickets-api.service.ts
// tickets-api.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
export type Ticket = {
id: string;
title: string;
status: string;
priority: string;
requester: string;
assignee?: string;
updatedAt: string;
};
@Injectable({ providedIn: "root" })
export class TicketsApiService {
private readonly baseUrl = "http://localhost:8080/api/tickets";
constructor(private readonly http: HttpClient) {}
create(payload: unknown) {
return this.http.post<Ticket>(this.baseUrl, payload);
}
triage(id: string, assignee: string) {
return this.http.post<Ticket>(`${this.baseUrl}/${id}/triage?assignee=${encodeURIComponent(assignee)}`, {});
}
approve(id: string) {
return this.http.post<Ticket>(`${this.baseUrl}/${id}/approve`, {});
}
}18.12 Frontend: React Kanban-Variante
type Ticket = {
id: string;
title: string;
status: string;
priority: string;
assignee?: string;
};
const columns = ["NEW", "TRIAGED", "IN_PROGRESS", "WAITING_FOR_APPROVAL", "APPROVED", "DONE"];
export function TicketKanbanBoard({ tickets }: { tickets: Ticket[] }) {
return (
<section className="kanban-board">
{columns.map((column) => {
const items = tickets.filter((ticket) => ticket.status === column);
return (
<article key={column} className="kanban-column">
<h2>{column}</h2>
{items.length === 0 && <p>Keine Tickets</p>}
{items.map((ticket) => (
<div key={ticket.id} className="ticket-card">
<strong>{ticket.title}</strong>
<span>{ticket.priority}</span>
<small>{ticket.assignee ?? "nicht zugewiesen"}</small>
</div>
))}
</article>
);
})}
</section>
);
}
18.13 Test für Statuslogik
package com.example.workflow.tickets;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class TicketTest {
@Test
void shouldMoveFromNewToTriaged() {
Ticket ticket = new Ticket("VPN Zugang", "Bitte VPN freischalten", "IT", TicketPriority.NORMAL, "aydin");
ticket.triage("it-agent-1");
assertThat(ticket.getStatus()).isEqualTo(TicketStatus.TRIAGED);
assertThat(ticket.getAssignee()).isEqualTo("it-agent-1");
}
@Test
void shouldNotApproveWithoutApprovalState() {
Ticket ticket = new Ticket("Budget", "Neuer Monitor", "Finance", TicketPriority.HIGH, "aydin");
assertThatThrownBy(ticket::approve)
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("WAITING_FOR_APPROVAL");
}
}18.14 AI-Aufgaben für Codex oder Claude Code
Implementiere für das Ticket-Projekt einen sauberen Workflow-Service.
Regeln:
- Erlaubte Statusübergänge zentral definieren.
- Ungültige Übergänge mit fachlicher Fehlermeldung ablehnen.
- AuditEvent bei jedem Übergang speichern.
- Unit Tests für alle erlaubten und verbotenen Übergänge schreiben.
- Keine Controller-Logik duplizieren.
Erstelle ein Angular Ticket-Dashboard.
Anforderungen:
- Tickets nach Status gruppieren.
- Filter für Kategorie, Priorität und Assignee.
- Loading, Error und Empty State.
- Klick auf Ticket öffnet Detailansicht.
- API Service zentral verwenden.
- Komponenten klein und testbar halten.
Führe ein Architekturreview für das Ticket & Approval Projekt durch.
Bewerte:
- Domänenmodell
- Statusmaschine
- Auditierbarkeit
- Rollen und Berechtigungen
- Testabdeckung
- Erweiterbarkeit für SLA und Benachrichtigungen
Gib konkrete Verbesserungen mit Aufwand und Risiko.
18.15 Abnahmekriterien
18.16 Enterprise-Erweiterungen
| Erweiterung | Beschreibung |
|---|---|
| SLA Engine | Fälligkeit abhängig von Kategorie und Priorität berechnen |
| Benachrichtigungen | E-Mail, Slack/Teams oder In-App Notifications |
| Anhänge | Dateien sicher hochladen und virenprüfen |
| Mandantenfähigkeit | Tickets pro Organisation oder Kunde trennen |
| Reporting | Durchlaufzeiten, Engpässe und Team-Auslastung messen |
| Outbox Pattern | Statusereignisse zuverlässig an andere Systeme senden |
| SSO | Login über Unternehmens-Identity-Provider |
| Berechtigungen | Sichtbarkeit nach Team, Rolle und Kategorie einschränken |
19. Beispielprojekt: WordPress Pizza-Restaurant mit Lieferung, SEO, Sicherheit und App
Dieses Beispielprojekt ist ein professionelles WordPress-Projekt für ein Pizza-Restaurant mit Online-Bestellung, lokaler Lieferung, Abholung, responsivem Design, PWA-App und optionaler nativer App. Budget spielt hier keine Rolle. Deshalb wird nicht der billigste Weg gewählt, sondern ein stabiler, performanter und sicherer Enterprise-Ansatz.
Projektname: Pizza Delivery Pro
Ziel: Eine schnelle, suchmaschinenoptimierte und sichere Website für ein Pizza-Restaurant, auf der Kunden Pizza, Getränke und Menüs bestellen, Lieferzeit wählen, online bezahlen und die Seite als App nutzen können.
19.1 Zielbild
Erklärung: Warum WordPress für dieses Projekt sinnvoll sein kann
Für ein Pizza-Restaurant ist Geschwindigkeit bei Umsetzung, Bedienbarkeit und lokaler Sichtbarkeit entscheidend. WordPress ist hier sinnvoll, weil Inhalte, Speisekarte, Angebote, Blog, Landingpages und rechtliche Seiten sehr schnell gepflegt werden können. WooCommerce ergänzt Warenkorb, Checkout, Zahlungen und Bestellungen. Das Restaurant-Team kann später Preise, Öffnungszeiten, Aktionen und Lieferinformationen ändern, ohne jedes Mal Entwickler zu brauchen.
Der Nachteil: WordPress-Projekte werden oft langsam oder unsicher, wenn zu viele Plugins, schlechte Themes oder ungeprüfte Erweiterungen genutzt werden. Deshalb setzt dieses Beispiel auf eine Premium-Architektur: schlankes Theme, kontrollierte Plugin-Auswahl, WAF, Backups, Staging, Updates, Security-Monitoring und klare Rollen. Das Ziel ist nicht „irgendeine schöne Website“, sondern eine bestellfähige, schnelle und sichere Verkaufsplattform.
| Entscheidung | Warum sie wichtig ist |
|---|---|
| Bricks statt überladenes Nischen-Theme | Bessere Kontrolle über Layout, Performance und WooCommerce-Templates. |
| WooCommerce | Standardisierte Produkt-, Warenkorb-, Checkout- und Bestelllogik. |
| Food-Ordering-Plugin | Restaurant-spezifische Optionen wie Extras, Lieferzeiten und Abholung. |
| PWA zuerst | App-ähnliches Erlebnis ohne sofort native iOS/Android-Entwicklung. |
| Cloudflare + WordPress-Härtung | Schutz auf Netzwerk-, Login-, Datei- und Anwendungsebene. |
| Local SEO | Restaurantkunden suchen häufig lokal und mobil. |
Merksatz: Bei einem Restaurant-Projekt ist die Website nicht nur Visitenkarte. Sie ist Vertriebskanal, Bestellsystem, SEO-Landingpage und Kundenbindungswerkzeug zugleich.
| Bereich | Ziel |
|---|---|
| Website | Moderne Restaurant-Website mit Menü, Liefergebiet, Aktionen, Bewertungen, Blog und lokalen Landingpages |
| Shop | WooCommerce für Warenkorb, Checkout, Coupons, Zahlungen und Bestellverwaltung |
| Lieferung | Lieferzonen, Mindestbestellwert, Abholoption, Zeitfenster, Öffnungszeiten, Feiertage |
| Theme | Performance-first Theme, kein überladenes ThemeForest-Template |
| SEO | Local SEO, strukturierte Daten, schnelle Ladezeiten, Stadtteilseiten, Pizza-Menü-Seiten |
| Sicherheit | WAF, Malware-Scan, 2FA, Backups, Rollenmodell, Updates, Monitoring, Staging |
| Responsive | Mobile-first Layout für Bestellungen auf Smartphone, Tablet und Desktop |
| App | PWA als erste App-Stufe, optional native iOS/Android App über WordPress/WooCommerce APIs |
19.2 Beste Theme-Entscheidung
Erklärung: Theme-Entscheidung im Detail
Die Theme-Wahl beeinflusst langfristig fast alles: Ladezeit, Wartbarkeit, Designfreiheit, WooCommerce-Templates, Barrierefreiheit und Update-Risiko. Ein fertiges Pizza-Theme sieht oft sofort passend aus, aber es bringt manchmal alte Page-Builder, viele Demo-Assets oder starre Layouts mit. Das kann später problematisch werden, wenn du Checkout, Produktkarten, Lieferzonen oder Landingpages individuell optimieren willst.
Bricks ist hier die Premium-Empfehlung, weil du ein eigenes Designsystem aufbauen kannst: Farben, Abstände, Buttons, Karten, Produkt-Layouts, Header, Footer und responsive Regeln werden einmal sauber definiert und dann wiederverwendet. Für SEO und Conversion ist das besser als ein rein dekoratives Theme. Ein Pizza-Restaurant braucht nicht nur hübsche Bilder, sondern klare Bestellwege, schnelle Seiten, gute mobile Bedienung und zuverlässige WooCommerce-Templates.
Wann Astra Pro trotzdem besser sein kann: Wenn das Projekt extrem schnell online gehen muss, das Team wenig technische Erfahrung hat und ein Starter-Template bevorzugt. Astra ist dann der pragmatische Weg. Bei „Budget spielt keine Rolle“ und maximaler Kontrolle ist Bricks plus individuelles Designsystem aber die stärkere Wahl.
Empfehlung ohne Budgetlimit: Bricks Builder als Theme/Visual Site Builder mit eigenem Pizza-Designsystem.
Warum nicht einfach irgendein fertiges „Pizza Theme“?
- Viele Nischen-Themes sehen am Anfang gut aus, bringen aber zu viel CSS, JS und Plugin-Abhängigkeiten mit.
- Für SEO, Core Web Vitals, Sicherheit und Wartbarkeit ist ein schlankes Theme mit sauberer Struktur besser.
- Mit Bricks kannst du Header, Footer, WooCommerce-Templates, Produktseiten, Checkout-Layouts und Landingpages gezielt bauen.
- Das Design wird individuell für die Marke erstellt: nicht wie tausend andere Pizza-Websites.
Alternative, wenn sehr schnelle Umsetzung wichtiger ist: Astra Pro mit Restaurant-/Food-Starter-Template und WooCommerce. Astra ist gut, wenn du ein bewährtes, leicht verständliches Theme mit vielen Vorlagen willst.
Entscheidung für dieses Projekt:
Theme: Bricks Builder
Designsystem: Custom Pizza Brand System
Shop: WooCommerce
Food Ordering: Orderable Pro oder WooCommerce Restaurant Ordering
SEO: Yoast SEO Premium + Yoast Local SEO + WooCommerce SEO
Security: Cloudflare WAF + Wordfence Premium/Care + serverseitige Härtung
Performance: Managed WordPress Hosting + CDN + Object Cache + Bildoptimierung
App: PWA zuerst, native App optional über WooCommerce/WordPress REST API
19.3 Architektur
Erklärung: Wie die Architektur zusammenspielt
Die Architektur trennt mehrere Verantwortlichkeiten. Cloudflare sitzt vor der Website und schützt sowie beschleunigt den Traffic. WordPress verwaltet Inhalte, Seiten, Medien und Benutzer. WooCommerce übernimmt Produkte, Warenkorb, Checkout und Bestellungen. Das Food-Ordering-Plugin ergänzt Restaurantfunktionen wie Extras, Lieferzeitfenster und Abholung. Der Payment Provider verarbeitet Zahlungen, damit keine sensiblen Kartendaten im eigenen System gespeichert werden müssen.
Für die Küche ist ein klares Order-Dashboard wichtig. Dort zählt nicht Design, sondern Geschwindigkeit: neue Bestellung sehen, Status ändern, Bon drucken, Lieferzeit abschätzen. Für Kunden zählt dagegen die mobile Bestellstrecke: schnell Menü öffnen, Pizza konfigurieren, Lieferadresse prüfen, bezahlen, Bestätigung bekommen.
Wichtig: Nicht jede Seite darf gleich gecacht werden. Startseite, Blog und Speisekarte können stark profitieren. Warenkorb, Checkout, Konto und personalisierte Bestellseiten müssen vorsichtig behandelt werden, weil sonst falsche Warenkorb- oder Kundendaten angezeigt werden könnten.
flowchart LR
Customer[Mobile/Desktop Kunde]
PWA[PWA / Website]
WP[WordPress]
WC[WooCommerce]
Food[Food Ordering Plugin]
Pay[Payment Provider]
DB[(Managed DB)]
CDN[Cloudflare CDN/WAF]
App[Native App optional]
Kitchen[Küche / Order Dashboard]
Customer --> CDN
CDN --> PWA
PWA --> WP
WP --> WC
WC --> Food
WC --> Pay
WP --> DB
Food --> Kitchen
App --> WP
App --> WC
19.4 Empfohlener Premium-Stack
| Kategorie | Empfehlung | Zweck |
|---|---|---|
| Hosting | Managed WordPress Hosting mit Staging, Backups, Redis/Object Cache | Stabilität, Performance, schnelle Wiederherstellung |
| CDN/WAF | Cloudflare Pro/Business/Enterprise | DDoS-Schutz, WAF, Caching, Bot-Schutz |
| Theme | Bricks Builder | Schnelle, individuelle, responsive WordPress-Seiten |
| Shop | WooCommerce | Produkte, Warenkorb, Checkout, Zahlungen, Bestellungen |
| Food Ordering | Orderable Pro oder WooCommerce Restaurant Ordering | Restaurant-Menü, Extras, Liefer-/Abhollogik |
| Delivery Slots | Order Delivery Date oder Delivery Time Slots | Lieferzeiten und Abholzeiten |
| SEO | Yoast SEO Premium + Yoast Local SEO + WooCommerce SEO | Local SEO, Schema, Produkt-SEO, interne Verlinkung |
| Security | Wordfence Premium/Care oder vergleichbarer Managed-Security-Service | Firewall, Malware-Scan, Login-Schutz, Incident Response |
| Backup | BlogVault, Jetpack VaultPress oder Hosting-Backup plus Offsite-Backup | Wiederherstellung nach Fehler oder Angriff |
| Performance | ShortPixel/Imagify, Perfmatters, Redis Object Cache | Bilder, Assets, Datenbank und Cache optimieren |
| Analytics | Google Search Console, GA4, Matomo optional | SEO- und Conversion-Auswertung |
| SMTP-Dienst wie Postmark, Brevo oder Mailgun | Zuverlässige Bestell- und Kundenmails |
19.5 Funktionsumfang
- Startseite mit Hero, Liefergebiet, CTA „Jetzt bestellen“.
- Online-Speisekarte mit Kategorien: Pizza, Pasta, Salate, Desserts, Getränke, Menüs.
- Produktoptionen: Größe, Teig, Extra-Käse, Schärfe, Zutaten entfernen, Zusatzbeläge.
- Liefergebiet nach PLZ oder Radius.
- Mindestbestellwert pro Lieferzone.
- Lieferkosten nach Zone oder Entfernung.
- Abholung mit Uhrzeit-Auswahl.
- Lieferung mit Zeitfenster-Auswahl.
- Online-Zahlung plus Barzahlung bei Lieferung, falls gewünscht.
- Gutscheine, Mittagsmenü, Familienmenü, Studentenrabatt.
- Kundenkonto optional, Gast-Checkout aktiviert.
- Küche-Dashboard für neue Bestellungen.
- Automatische E-Mail/SMS/WhatsApp-Benachrichtigung optional.
- Bewertungsbereich und Google-Business-Verknüpfung.
- Blog für lokale SEO-Inhalte.
- PWA-Installation auf Smartphone.
- Optional native App für iOS und Android.
19.6 Seitenstruktur für SEO
/
/bestellen/
/speisekarte/
/speisekarte/pizza/
/speisekarte/pasta/
/speisekarte/getraenke/
/angebote/
/liefergebiet/
/liefergebiet/wien-1010/
/liefergebiet/wien-1020/
/liefergebiet/wien-1030/
/abholung/
/ueber-uns/
/kontakt/
/blog/
/blog/beste-pizza-in-wien/
/blog/pizza-lieferung-wien-abends/
/datenschutz/
/impressum/
/agb/
19.7 SEO-Strategie
Erklärung: SEO bei einem Pizza-Lieferdienst
SEO für ein Restaurant ist stark lokal. Ein Kunde sucht meistens nicht allgemein nach „Pizza“, sondern nach „Pizza bestellen in meiner Nähe“, „Pizza Lieferung Wien 1020“ oder „Pizzeria offen jetzt“. Deshalb braucht das Projekt eine klare lokale Struktur: konsistente Name-Adresse-Telefon-Daten, Google Business Profile, Öffnungszeiten, Liefergebiet, Produktseiten und echte lokale Inhalte.
Stadtteilseiten sind nur dann sinnvoll, wenn sie echten Nutzen haben. Eine Seite für „Pizza Lieferung Wien 1020“ sollte also nicht nur denselben Text mit anderer PLZ enthalten. Sie sollte Lieferbedingungen, ungefähre Lieferzeit, Mindestbestellwert, beliebte Produkte, lokale Hinweise und klare Bestell-CTA enthalten. Sonst wirkt sie wie eine Doorway Page und bringt langfristig wenig.
| SEO-Element | Erklärung |
|---|---|
| LocalBusiness/Restaurant Schema | Hilft Suchmaschinen, Restaurantdaten strukturiert zu verstehen. |
| Produktdaten | Jede Pizza kann als kaufbares Produkt mit Preis und Angebot verstanden werden. |
| Interne Verlinkung | Führt Nutzer und Suchmaschinen von Startseite zu Speisekarte, Angeboten und Liefergebieten. |
| Core Web Vitals | Mobile Geschwindigkeit beeinflusst Nutzererlebnis und Conversion. |
| Bewertungen | Vertrauen und lokale Sichtbarkeit werden gestärkt. |
| FAQ | Klärt Lieferzeit, Zahlung, Allergene, Mindestbestellwert und Abholung. |
Praxisziel: SEO soll nicht nur Besucher bringen, sondern Bestellungen. Deshalb müssen Ranking-Seiten immer mit einer klaren Bestellmöglichkeit verbunden sein.
| SEO-Bereich | Umsetzung |
|---|---|
| Local SEO | Google Business Profile, identische NAP-Daten, Öffnungszeiten, Telefonnummer, Adresse, Liefergebiet |
| Strukturierte Daten | Restaurant, LocalBusiness, PostalAddress, OpeningHoursSpecification, Menu, Product, Offer |
| Keyword-Cluster | „Pizza Lieferung + Stadt“, „Pizza bestellen + Stadtteil“, „beste Pizza + Stadt“, „Pizzeria in der Nähe“ |
| Landingpages | Eigene Seiten pro Liefergebiet, aber mit echtem lokalen Inhalt statt dupliziertem Text |
| Produkt-SEO | Jede Pizza mit Name, Beschreibung, Zutaten, Allergenen, Bild, Preis, Varianten |
| Performance | Mobile LCP optimieren, Bilder komprimieren, Fonts lokal, unnötige Plugins vermeiden |
| Content | Blog über Aktionen, Zutaten, lokale Events, Lieferzeiten, Pizza-Arten |
| Interne Links | Startseite → Speisekarte → Kategorien → Bestseller → Liefergebiet |
| Conversion | Sticky „Jetzt bestellen“-Button auf Mobile, klare Mindestbestellwerte, schnelle Checkout-Strecke |
19.8 Beispiel: JSON-LD für Restaurant
Werte wie Adresse, Telefon, Öffnungszeiten und URL musst du im echten Projekt ersetzen.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Restaurant",
"name": "Pizza Delivery Pro",
"image": "https://example.com/wp-content/uploads/pizza-margherita.jpg",
"url": "https://example.com/",
"telephone": "+43-1-1234567",
"servesCuisine": ["Pizza", "Italian", "Pasta"],
"priceRange": "€€",
"address": {
"@type": "PostalAddress",
"streetAddress": "Beispielstraße 12",
"addressLocality": "Wien",
"postalCode": "1010",
"addressCountry": "AT"
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "11:00",
"closes": "23:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Saturday", "Sunday"],
"opens": "12:00",
"closes": "23:30"
}
],
"hasMenu": "https://example.com/speisekarte/",
"acceptsReservations": false,
"areaServed": ["Wien 1010", "Wien 1020", "Wien 1030"]
}
</script>19.9 Responsive Design-System
Erklärung: Responsive und App-ähnliche Bedienung
Bei einem Pizza-Lieferdienst kommt ein großer Teil der Bestellungen über Smartphones. Deshalb muss Mobile nicht nur „irgendwie passen“, sondern zuerst geplant werden. Ein Desktop-Layout mit drei Spalten funktioniert auf dem Handy nicht automatisch gut. Mobile Nutzer wollen schnell Kategorien wechseln, Produkte konfigurieren, den Warenkorb sehen und ohne lange Formulare bestellen.
Die Sticky-Bestellleiste ist deshalb kein Deko-Element, sondern ein Conversion-Werkzeug. Sie zeigt dem Nutzer jederzeit, dass er bestellen kann und wie er zum Warenkorb kommt. Produktkarten müssen große Buttons haben, Optionen dürfen nicht versteckt sein, und die PLZ-Prüfung sollte früh erfolgen. Es ist frustrierend, wenn ein Kunde erst am Ende erfährt, dass seine Adresse nicht beliefert wird.
Für die App-Strategie gilt: Eine PWA ist der beste erste Schritt, weil sie schnell verfügbar ist, installierbar wirkt und keine App-Store-Freigabe braucht. Eine native App lohnt sich erst, wenn es echte Stammkunden-Funktionen gibt: Push-Nachrichten, Favoriten, Reorder, Loyalty, Gutscheine oder Fahrertracking.
:root {
--color-tomato: #d62828;
--color-cheese: #fcbf49;
--color-basil: #2a9d8f;
--color-crust: #f4a261;
--color-ink: #1f2937;
--color-surface: #fffaf0;
--radius-card: 18px;
--shadow-card: 0 16px 40px rgba(31, 41, 55, 0.12);
--font-heading: "Inter", system-ui, sans-serif;
--font-body: "Inter", system-ui, sans-serif;
}
.pizza-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 1rem;
}
@media (min-width: 640px) {
.pizza-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.pizza-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.mobile-order-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.85rem 1rem;
background: var(--color-tomato);
color: white;
}
@media (min-width: 900px) {
.mobile-order-bar {
display: none;
}
}19.10 Mobile UX-Regeln
| Bereich | Regel |
|---|---|
| Hero | Ein CTA: „Jetzt bestellen“, nicht mehrere konkurrierende Buttons |
| Menü | Kategorien als horizontale Sticky-Tabs |
| Produktkarte | Bild, Name, Preis, Bestseller-Badge, „Hinzufügen“-Button |
| Checkout | So wenig Felder wie möglich, Gastbestellung erlauben |
| Lieferadresse | PLZ zuerst prüfen, dann Adresse erfassen |
| Warenkorb | Sticky Summary auf Mobile |
| Geschwindigkeit | Keine schweren Slider, keine Autoplay-Videos auf Startseite |
| Barrierefreiheit | Buttons groß genug, Kontrast prüfen, Fokus-Stile sichtbar |
19.11 Lokale Entwicklungsumgebung mit Docker
services:
wordpress:
image: wordpress:php8.3-apache
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: pizza_wp
volumes:
- ./wp-content:/var/www/html/wp-content
depends_on:
- db
db:
image: mariadb:11
environment:
MYSQL_DATABASE: pizza_wp
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_ROOT_PASSWORD: root
volumes:
- db_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin:latest
ports:
- "8081:80"
environment:
PMA_HOST: db
depends_on:
- db
volumes:
db_data:Start:
mkdir pizza-delivery-pro
cd pizza-delivery-pro
mkdir wp-content
# docker-compose.yml anlegen
docker compose up -dWordPress lokal öffnen:
http://localhost:8080
19.12 WooCommerce Produktmodell für Pizza
| Produkt | Typ | Varianten/Optionen |
|---|---|---|
| Pizza Margherita | Variables Product | 26 cm, 32 cm, Familie |
| Pizza Salami | Variables Product | Größe, extra Käse, scharf |
| Pizza Vegetaria | Variables Product | Größe, glutenfrei optional |
| Pizza Diavolo | Variables Product | Größe, Schärfegrad |
| Getränke | Simple Product | Cola, Fanta, Wasser |
| Menü 1 | Bundle/Composite | Pizza + Getränk + Dessert |
Empfohlene Kategorien:
Pizza
Pasta
Salate
Desserts
Getränke
Menüs
Angebote
Vegetarisch
Scharf
Bestseller
19.13 Checkout-Logik
Erklärung: Warum Checkout-Logik geschäftskritisch ist
Der Checkout entscheidet direkt über Umsatz. Jede unnötige Hürde kann Bestellungen abbrechen lassen. Für ein Pizza-Restaurant sind drei Dinge besonders wichtig: Lieferfähigkeit, korrekte Zeitfenster und transparente Kosten. Der Kunde sollte früh sehen, ob seine PLZ beliefert wird, welcher Mindestbestellwert gilt und wann die Bestellung ungefähr ankommt.
Die Regeln dürfen nicht nur im Frontend stehen. Ein Benutzer könnte Frontend-Prüfungen umgehen oder ein Fehler könnte falsche Werte senden. Deshalb müssen Lieferzone, Mindestbestellwert, Öffnungszeiten, Liefergebühren und verfügbare Zeitfenster serverseitig geprüft werden. Das Frontend hilft nur bei der Bedienung; die Wahrheit liegt im Backend beziehungsweise in WooCommerce/Plugin-Regeln.
| Regel | Warum sie serverseitig geprüft werden muss |
|---|---|
| Lieferzone | Sonst werden Bestellungen außerhalb des Gebiets angenommen. |
| Mindestbestellwert | Sonst entstehen unrentable Lieferungen. |
| Lieferzeitfenster | Sonst bestellt jemand außerhalb der Küchenkapazität. |
| Zahlungsart | Nicht jede Zahlungsart sollte für jede Zone/Bestellung erlaubt sein. |
| Coupon | Rabatte dürfen nicht falsch kombiniert werden. |
Optimierung: Gast-Checkout aktivieren, aber Kundenkonto nach der Bestellung anbieten. So wird der erste Kauf nicht blockiert, aber Stammkunden können später schneller bestellen.
Kunde öffnet /bestellen
→ PLZ prüfen
→ Lieferzone bestimmen
→ Mindestbestellwert anzeigen
→ Produkte hinzufügen
→ Lieferzeit oder Abholzeit wählen
→ Adresse bestätigen
→ Zahlung wählen
→ Bestellung abschicken
→ Küche bekommt Bestellung
→ Kunde bekommt Bestellbestätigung
Regeln:
Wenn PLZ nicht im Liefergebiet:
Abholung anbieten
Wenn Warenkorb < Mindestbestellwert:
Differenz anzeigen
Wenn Restaurant geschlossen:
Vorbestellung für nächsten Slot anbieten
Wenn Küche ausgelastet:
nächste freie Lieferzeit anzeigen
19.14 App-Strategie
Phase 1: PWA
- Website installierbar auf Smartphone.
- App-Icon auf Homescreen.
- Schneller Zugriff auf Speisekarte und Bestellung.
- Offline-Fallback für Menü-Seiten.
- Push-Benachrichtigung nur, wenn sauber umgesetzt und rechtlich geprüft.
Phase 2: Native App optional
- React Native oder Flutter.
- WordPress REST API für Inhalte.
- WooCommerce REST API oder Store API für Produkte, Warenkorb und Bestellung.
- Eigene App-Funktionen: Reorder, Favoriten, Push, Loyalty-System, Gutschein-Wallet.
Empfehlung: Erst PWA bauen und echte Nutzungsdaten sammeln. Native App erst erstellen, wenn wiederkehrende Kunden, Loyalty und Push-Marketing wirklich relevant sind.
19.15 PWA Manifest
Datei: app.webmanifest
{
"id": "/?source=pwa",
"name": "Pizza Delivery Pro",
"short_name": "PizzaPro",
"description": "Pizza online bestellen mit Lieferung und Abholung.",
"start_url": "/bestellen/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#fffaf0",
"theme_color": "#d62828",
"orientation": "portrait-primary",
"icons": [
{
"src": "/wp-content/uploads/app-icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/wp-content/uploads/app-icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "Jetzt bestellen",
"url": "/bestellen/",
"description": "Pizza bestellen"
},
{
"name": "Angebote",
"url": "/angebote/",
"description": "Aktuelle Pizza-Angebote"
}
]
}In WordPress einbinden:
add_action('wp_head', function () {
echo '<link rel="manifest" href="/app.webmanifest">' . PHP_EOL;
echo '<meta name="theme-color" content="#d62828">' . PHP_EOL;
});19.16 Service Worker Minimalbeispiel
Datei: service-worker.js
const CACHE_NAME = 'pizza-delivery-pro-v1';
const OFFLINE_URLS = [
'/',
'/speisekarte/',
'/bestellen/',
'/wp-content/uploads/app-icon-192.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(OFFLINE_URLS))
);
});
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request).then(response => {
return response || caches.match('/speisekarte/');
}))
);
});Registrierung:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>19.17 Headless App API-Idee
Für eine spätere native App oder ein eigenes React/Next.js-Frontend kannst du WordPress und WooCommerce als Backend verwenden.
GET /wp-json/wp/v2/pages
GET /wp-json/wp/v2/posts
GET /wp-json/wc/store/v1/products
GET /wp-json/wc/store/v1/products/categories
POST /wp-json/wc/store/v1/cart/add-item
GET /wp-json/wc/store/v1/cart
POST /wp-json/wc/store/v1/checkout
Beispiel: Produkte für die App abrufen
export async function loadPizzaProducts() {
const response = await fetch('https://example.com/wp-json/wc/store/v1/products?category=pizza');
if (!response.ok) {
throw new Error('Produkte konnten nicht geladen werden');
}
return response.json();
}19.18 Sicherheitskonzept
Erklärung: Security als Schichtenmodell
WordPress-Sicherheit funktioniert am besten in mehreren Schichten. Keine einzelne Maßnahme reicht allein aus. Cloudflare kann Angriffe filtern, aber schützt nicht vor einem unsicheren Admin-Passwort. Wordfence kann Malware erkennen, aber ersetzt keine Backups. Backups helfen bei Wiederherstellung, verhindern aber keinen Angriff. Deshalb kombiniert das Konzept Netzwerk-Schutz, sichere Logins, minimale Rechte, vertrauenswürdige Plugins, Updates, Monitoring und Restore-Tests.
Besonders wichtig ist das Rollenmodell. Der Restaurantbesitzer braucht nicht dauerhaft Administratorrechte, um Bestellungen zu sehen. Küchenpersonal braucht keine Plugin-Installation. Marketing braucht keinen Zugriff auf Security-Einstellungen. Je weniger Rechte ein Konto hat, desto kleiner ist der Schaden bei Passwortverlust oder Fehlbedienung.
| Sicherheitsfrage | Richtige Denkweise |
|---|---|
| „Haben wir Backups?“ | Wichtig, aber nur ein getesteter Restore zählt. |
| „Sind Plugins aktuell?“ | Updates zuerst in Staging testen, dann Produktion aktualisieren. |
| „Ist Wordfence installiert?“ | Gut, aber zusätzlich WAF, 2FA und Rechte prüfen. |
| „Kann jeder Admin sein?“ | Nein, Rollen nach Aufgabe trennen. |
| „Brauchen wir CSP?“ | Ja, aber vorsichtig mit Report-Only starten, weil Payment/Analytics externe Skripte nutzen. |
Notfallregel: Vor jedem größeren Update Backup erstellen, Staging testen, Change dokumentieren und Rückrollplan kennen.
| Ebene | Maßnahme |
|---|---|
| DNS/CDN | Cloudflare mit WAF, Bot-Schutz, Rate Limiting, DDoS-Schutz |
| Hosting | Managed Hosting, isolierte Umgebung, tägliche Backups, Staging |
| WordPress | Auto-Updates für Minor Releases, Update-Prozess für Plugins/Themes |
| Login | 2FA für Admins, starke Passwörter, Login-Limits, keine geteilten Accounts |
| Rollen | Admin nur für technische Verantwortliche, Shop Manager für Bestellungen |
| Plugins | Nur notwendige Plugins, Premium-Plugins aus vertrauenswürdigen Quellen |
| Dateien | Schreibrechte begrenzen, Plugin-/Theme-Editor deaktivieren |
| Malware | Wordfence Scan, serverseitige Malware-Prüfung, externe Uptime-Checks |
| Backups | Offsite-Backups, Restore-Test monatlich |
| Zahlungen | Keine Kreditkartendaten selbst speichern; seriöse Payment Provider nutzen |
| Datenschutz | DSGVO-konforme Cookie-/Consent-Lösung, Auftragsverarbeitungsverträge prüfen |
19.19 wp-config.php Härtung
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', false);
define('FORCE_SSL_ADMIN', true);
define('WP_AUTO_UPDATE_CORE', 'minor');
define('EMPTY_TRASH_DAYS', 7);
define('WP_POST_REVISIONS', 10);
// Debug niemals in Produktion sichtbar lassen.
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', false);19.20 Security Headers
Beispiel für Nginx:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self)" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Content Security Policy vorsichtig einführen, weil WordPress, WooCommerce, Payment Provider, Maps, Analytics und Consent-Tools externe Skripte verwenden können.
Start mit Report-Only:
add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; connect-src 'self' https:; frame-src https:;" always;
19.21 Performance-Plan
Erklärung: Performance ohne kaputten Warenkorb
Performance-Optimierung bei WooCommerce ist anders als bei einer reinen Content-Website. Eine normale Blogseite kann aggressiv gecacht werden. Ein Warenkorb oder Checkout ist dynamisch und benutzerspezifisch. Wenn du hier falsch cachst, sehen Kunden im schlimmsten Fall falsche Warenkörbe oder Checkout-Zustände. Deshalb muss die Caching-Strategie zwischen öffentlichen Seiten und transaktionalen Seiten unterscheiden.
Öffentliche Seiten wie Startseite, Liefergebiet, Blog und Speisekarte können über Page Cache und CDN sehr schnell werden. Produktseiten können teilweise gecacht werden, solange Warenkorb-Fragmente und Preise korrekt bleiben. Cart, Checkout, Mein Konto und Bestellbestätigung sollten aus dem aggressiven Cache ausgeschlossen werden. Object Cache hilft zusätzlich, Datenbankabfragen schneller zu machen, ohne komplette Seiten falsch auszuliefern.
Messpunkte vor Launch: Mobile LCP, CLS, Checkout-Ladezeit, Server-TTFB, Bildgrößen, Plugin-Anzahl, Datenbank-Autoload-Optionen und WooCommerce-Logs. Performance ist nicht nur ein SEO-Thema, sondern beeinflusst direkt die Bestellquote.
| Thema | Umsetzung |
|---|---|
| Bilder | WebP/AVIF, korrekte Bildgrößen, Lazy Loading |
| Fonts | Lokale Fonts, maximal 1–2 Familien, font-display: swap |
| CSS/JS | Nur benötigte Bricks-/Plugin-Assets laden, keine schweren Slider |
| Cache | Page Cache für nicht-personalisierte Seiten, Object Cache für Datenbank |
| Warenkorb | Cart/Checkout nicht aggressiv cachen |
| CDN | Statische Assets über CDN ausliefern |
| Datenbank | Autoloaded Options prüfen, Transients bereinigen |
| Monitoring | Core Web Vitals, Uptime, Error Logs, WooCommerce Status Logs |
19.22 SEO-Contentplan für 8 Wochen
| Woche | Inhalt |
|---|---|
| 1 | Startseite, Speisekarte, Bestellseite, Kontakt, Liefergebiet |
| 2 | Produkttexte für Top-20-Pizzen optimieren |
| 3 | Stadtteilseiten für wichtigste Liefergebiete erstellen |
| 4 | Blogartikel: „Beste Pizza in [Stadt] bestellen“ |
| 5 | Blogartikel: „Pizza Lieferung am Abend in [Stadt]“ |
| 6 | Angebotsseite für Familienmenüs und Mittagsmenü |
| 7 | FAQ-Seite zu Lieferzeiten, Allergenen, Zahlung, Abholung |
| 8 | Schema, interne Links, Search Console, Ranking-Review |
19.23 Projektstruktur für Dokumentation
pizza-delivery-pro/
├─ docs/
│ ├─ brief.md
│ ├─ seo-keywords.md
│ ├─ security-checklist.md
│ ├─ content-plan.md
│ ├─ app-strategy.md
│ └─ launch-checklist.md
├─ wp-content/
│ ├─ themes/
│ │ └─ bricks-child-pizza/
│ │ ├─ style.css
│ │ ├─ functions.php
│ │ └─ assets/
│ ├─ plugins/
│ └─ uploads/
├─ docker-compose.yml
└─ _intern/sources/_intern\sources\README.md
19.24 README-Vorlage
# Pizza Delivery Pro
WordPress/WooCommerce-Projekt für ein Pizza-Restaurant mit Lieferung, Abholung, SEO, Security und PWA.
## Stack
- WordPress
- WooCommerce
- Bricks Builder
- Orderable Pro oder WooCommerce Restaurant Ordering
- Yoast SEO Premium, Local SEO, WooCommerce SEO
- Cloudflare WAF
- Wordfence Premium/Care
- PWA Manifest + Service Worker
## Lokaler Start
```bash
docker compose up -d
```
Website:
```text
http://localhost:8080
```
phpMyAdmin:
```text
http://localhost:8081
```
## Launch-Check
- [ ] SSL aktiv
- [ ] Cloudflare aktiv
- [ ] WAF-Regeln aktiv
- [ ] Admin 2FA aktiv
- [ ] Backups getestet
- [ ] WooCommerce Checkout getestet
- [ ] Zahlung getestet
- [ ] Lieferzonen getestet
- [ ] Schema geprüft
- [ ] Search Console verbunden
- [ ] PWA installierbar19.25 Codex/Claude-Code Prompts
Du bist Senior WordPress/WooCommerce Architekt. Analysiere dieses Pizza-Restaurant-Projekt und erstelle eine technische Umsetzungsliste für Theme, WooCommerce, Lieferung, SEO, Security und PWA. Teile die Arbeit in kleine GitHub-Issues mit Akzeptanzkriterien auf.
Erstelle für dieses WordPress-Projekt ein Sicherheitskonzept. Berücksichtige Cloudflare WAF, Wordfence, Rollen, 2FA, Plugin-Auswahl, Backups, Restore-Test, Staging, Payment-Sicherheit und DSGVO. Gib konkrete technische Maßnahmen und Prüfpunkte aus.
Erstelle eine SEO-Struktur für ein Pizza-Restaurant mit Lieferung in Wien. Baue Seitenstruktur, Keyword-Cluster, LocalBusiness/Restaurant Schema, interne Verlinkung, Blogplan und Produktbeschreibungsregeln. Vermeide Doorway Pages und duplizierte Stadtteiltexte.
Erstelle ein responsives UI-Konzept für eine mobile Pizza-Bestellseite. Fokus: Sticky Warenkorb, schnelle Kategorie-Navigation, Produktoptionen, PLZ-Prüfung, Mindestbestellwert, Lieferzeit-Auswahl und barrierearme Buttons.
Erstelle eine PWA-Strategie für diese WordPress/WooCommerce-Seite. Liefere Manifest, Service-Worker-Plan, Cache-Regeln, Offline-Fallback, App-Icons, Install-Hinweise und Grenzen gegenüber einer nativen App.
19.26 Abnahmekriterien
19.27 Erweiterungen
| Erweiterung | Nutzen |
|---|---|
| Loyalty-System | Punkte, Stempelkarte, Gutscheine |
| Reorder-Funktion | Stammkunden bestellen schneller erneut |
| Küchenmonitor | Bessere operative Bestellabwicklung |
| Fahrer-Modul | Lieferstatus und Tourenplanung |
| Multilocation | Mehrere Filialen mit eigener Lieferzone |
| Native App | Push, Login, Favoriten, Gutschein-Wallet |
| Headless Frontend | Next.js oder Nuxt für maximale Frontend-Kontrolle |
| CRM-Anbindung | Newsletter, Geburtstagsgutscheine, Segmentierung |
20. Prompt-Bibliothek für Codex und Claude Code
17.1 Projektanalyse
Analysiere dieses Repository für einen neuen Entwickler.
Erkläre:
- Projektziel
- Architektur
- Startbefehle
- wichtigste Module
- Teststrategie
- Risiken
Ändere keine Dateien.
17.2 Feature Plan
Ich möchte Feature X implementieren.
Erstelle zuerst einen technischen Plan mit:
- betroffene Dateien
- API Änderungen
- Datenmodell
- Tests
- Risiken
- Reihenfolge der Umsetzung
Ändere noch keine Dateien.
17.3 Backend Feature
Implementiere Feature X im Spring-Boot-Projekt.
Regeln:
- bestehende Architektur beibehalten
- DTOs nicht direkt als Entities verwenden
- Validation ergänzen
- Fehlerfälle behandeln
- Unit- und Integrationstests schreiben
- Tests ausführen
- keine unnötigen Dependencies
17.4 Frontend Feature
Implementiere Feature X im Frontend.
Regeln:
- TypeScript strikt verwenden
- vorhandene Komponenten/Styles nutzen
- Loading, Error und Empty State berücksichtigen
- API Client zentral verwenden
- Tests ergänzen
- keine neue UI Library ohne Begründung
17.5 Bugfix
Hier ist der Fehler/Stacktrace: ...
Finde die Ursache.
Erkläre zuerst Hypothesen und betroffene Dateien.
Ändere erst nach dem Plan.
Schreibe einen Regressionstest.
17.6 Refactoring
Refactore diesen Bereich, ohne Verhalten zu ändern.
Vorgehen:
- vorhandene Tests prüfen
- fehlende Tests ergänzen
- kleine Schritte
- keine public API ändern
- am Ende Diff zusammenfassen
17.7 Security Review
Führe ein Security Review für dieses Modul durch.
Fokus:
- Authentifizierung
- Autorisierung
- Input Validation
- Secrets
- Logging sensibler Daten
- SQL/Injection Risiken
Erstelle Findings mit Schweregrad und Fix-Vorschlag. Ändere noch nichts.
17.8 Architekturreview
Prüfe die Architektur dieses Projekts.
Bewerte:
- Modulgrenzen
- Abhängigkeiten
- Fachlogik-Ort
- Testbarkeit
- Datenbankzugriffe
- API Konsistenz
Gib konkrete Verbesserungsvorschläge mit Aufwand und Risiko.
21. Checklisten
18.1 Neues Java Enterprise Projekt
18.2 Neues Frontend Projekt
18.3 AI-Agent Aufgabe
18.4 Pull Request
22. Quellen und offizielle Doku
WordPress, WooCommerce, SEO, Security und App
- WordPress Developer Resources: https://developer.wordpress.org/
- WordPress REST API Handbook: https://developer.wordpress.org/rest-api/
- WordPress Security / Hardening: https://developer.wordpress.org/advanced-administration/security/hardening/
- WordPress.org Security: https://wordpress.org/about/security/
- WooCommerce Plugin: https://wordpress.org/plugins/woocommerce/
- WooCommerce REST API: https://developer.woocommerce.com/docs/apis/rest-api/
- WooCommerce Local Pickup: https://woocommerce.com/document/local-pickup/
- WooCommerce Order Delivery Date: https://woocommerce.com/products/order-delivery-date/
- WooCommerce Delivery Time Slots: https://woocommerce.com/document/delivery-time-slots-for-woocommerce/
- Orderable Restaurant Ordering: https://wordpress.org/plugins/orderable/
- Bricks Builder: https://bricksbuilder.io/
- Bricks WooCommerce Builder: https://academy.bricksbuilder.io/integrations/woocommerce/woocommerce-builder/
- Astra Theme: https://wpastra.com/
- Google Search Central Local Business Structured Data: https://developers.google.com/search/docs/appearance/structured-data/local-business
- Google Structured Data Introduction: https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data
- Yoast Local SEO: https://yoast.com/features/local-seo/
- Cloudflare WAF: https://developers.cloudflare.com/waf/
- Wordfence Help Documentation: https://www.wordfence.com/help/
- MDN Progressive Web Apps: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps
- MDN Web App Manifest: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
AI Coding Tools
- OpenAI Codex CLI: https://developers.openai.com/codex/cli
- OpenAI Codex Quickstart: https://developers.openai.com/codex/quickstart
- OpenAI Codex Config Reference: https://developers.openai.com/codex/config-reference
- Claude Code Overview: https://code.claude.com/docs/en/overview
- Claude Code Quickstart: https://code.claude.com/docs/en/quickstart
- Claude Code VS Code Integration: https://code.claude.com/docs/en/ide-integrations
- Claude Code MCP: https://code.claude.com/docs/en/mcp
Java und Backend
- OpenJDK JDK 25: https://openjdk.org/projects/jdk/25/
- Oracle Java Downloads: https://www.oracle.com/java/technologies/downloads/
- Spring Boot Project: https://spring.io/projects/spring-boot
- Spring Boot Getting Started: https://spring.io/guides/gs/spring-boot
- Spring Boot Docs: https://docs.spring.io/spring-boot/documentation.html
- Spring REST Guide: https://spring.io/guides/gs/rest-service
- Spring Boot Docker Compose Support: https://docs.spring.io/spring-boot/how-to/docker-compose.html
- Spring Boot Development-time Services: https://docs.spring.io/spring-boot/reference/features/dev-services.html
- Jakarta EE Specifications: https://jakarta.ee/specifications/
- Jakarta EE Tutorial: https://jakarta.ee/learn/docs/jakartaee-tutorial/9.1/intro/overview/overview.html
- Quarkus Getting Started: https://quarkus.io/guides/getting-started
- Micronaut Docs: https://docs.micronaut.io/latest/guide/
Frontend
- Node.js Releases: https://nodejs.org/en/about/previous-releases
- TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/intro.html
- React Docs: https://react.dev/learn/creating-a-react-app
- Vite Docs: https://vite.dev/guide/
- Vite Deutsche Anleitung: https://de.vitejs.dev/guide/
- Angular Docs: https://angular.dev/overview
- Angular CLI
ng new: https://angular.dev/cli/new - Angular CLI Setup: https://angular.dev/tools/cli/setup-local
- Next.js Docs: https://nextjs.org/docs
- Vue Quick Start: https://vuejs.org/guide/quick-start