15 KiB
GraphQL Middlelayer + Astro Frontend
Ein moderner, skalierbarer Onlineshop mit GraphQL-basiertem Middlelayer und Astro-Frontend. Der Middlelayer fungiert als flexible Abstraktionsschicht zwischen Frontend und verschiedenen Datenquellen.
🚀 Features
- GraphQL API mit Apollo Server v5
- Astro Frontend mit SSR und Client-Side Interaktivität
- Adapter Pattern für flexible Datenquellen (Mock, Headless CMS, Database)
- Performance-Optimierungen:
- Dataloader für Batch-Loading (verhindert N+1 Queries)
- Redis/In-Memory Caching
- Response Caching
- Query Complexity Limits
- Monitoring & Observability:
- Structured Logging (Winston)
- Prometheus Metrics
- Distributed Tracing
- Type-Safe mit TypeScript durchgängig
- Modern UI mit Tailwind CSS und Alpine.js
- Internationalization (i18n):
- URL-basierte Locales (
/de,/en) - Übersetzungen aus Middlelayer (mit Default-Fallback)
- CMS-Inhalte mehrsprachig (vorbereitet für Contentful)
- URL-basierte Locales (
📋 Voraussetzungen
- Node.js 18+
- npm oder yarn
- (Optional) Redis für verteiltes Caching
🛠️ Installation
# Dependencies installieren
npm install
# (Optional) Redis installieren (macOS)
brew install redis
brew services start redis
🏃 Quick Start
# GraphQL Server + Astro Frontend starten
npm start
# Oder einzeln:
npm run mock:server # Nur GraphQL Server (Port 4000)
npm run dev # Nur Astro Frontend (Port 4321)
📁 Projektstruktur
/
├── middlelayer/ # GraphQL Middlelayer
│ ├── __cms/ # Legacy Contentful-Typen (nicht mehr verwendet)
│ ├── adapters/ # Datenquellen-Adapter (Mock, CMS, etc.)
│ │ ├── Mock/ # Mock-Adapter mit Mock-Daten
│ │ │ ├── _cms/ # CMS Mock-Daten
│ │ │ └── _i18n/ # i18n Mock-Daten
│ │ ├── config.ts # Adapter-Konfiguration
│ │ └── interface.ts # DataAdapter Interface
│ ├── auth/ # Authentication & Authorization
│ ├── config/ # Konfiguration (Cache, etc.)
│ ├── mappers/ # Data Mapper (CMS → Domain)
│ │ └── pageMapper.ts # Page-Transformationen
│ ├── monitoring/ # Logging, Metrics, Tracing
│ ├── plugins/ # Apollo Server Plugins
│ ├── types/ # TypeScript Typen
│ │ ├── cms/ # CMS-spezifische Typen (für Mapper)
│ │ ├── c_*.ts # Domain Content-Item-Typen
│ │ └── *.ts # Weitere Domain-Typen
│ ├── utils/ # Utilities
│ │ ├── cache.ts # Cache-Implementierung
│ │ ├── cacheKeys.ts # Cache-Key-Builder
│ │ ├── dataServiceHelpers.ts # Helper für DataService
│ │ ├── dataloaders.ts # Dataloader + GraphQLContext
│ │ └── errors.ts # Error-Klassen
│ ├── dataService.ts # DataService (Singleton)
│ ├── index.ts # Server Entry Point
│ ├── schema.ts # GraphQL Schema
│ └── resolvers.ts # GraphQL Resolvers
│
├── src/ # Astro Frontend
│ ├── components/ # Astro Komponenten
│ │ ├── cms/ # CMS Content-Komponenten
│ │ └── ... # Weitere Komponenten
│ ├── layouts/ # Layout Templates
│ ├── lib/ # Utilities & GraphQL Client
│ │ ├── graphql/ # GraphQL Client & Queries
│ │ ├── i18n/ # i18n-System
│ │ └── utils/ # Utilities (Layout, Markdown, etc.)
│ ├── pages/ # Astro Pages
│ │ └── [locale]/ # Locale-basierte Routen
│ └── styles/ # Global Styles
│
└── docs/ # Dokumentation
⚙️ Konfiguration
Environment Variables
Erstelle eine .env Datei im Root-Verzeichnis:
# Server Ports
PORT=4000 # GraphQL Server Port
METRICS_PORT=9090 # Metrics Server Port
# Logging
LOG_LEVEL=info # debug, info, warn, error
NODE_ENV=development # development, production
# GraphQL
MAX_QUERY_COMPLEXITY=1000 # Query Complexity Limit
# Redis (Optional)
REDIS_ENABLED=true # Redis Cache aktivieren
REDIS_HOST=localhost # Redis Host
REDIS_PORT=6379 # Redis Port
REDIS_PASSWORD= # Optional: Redis Password
# Cache TTLs (in Millisekunden)
CACHE_PAGES_TTL=60000 # 60 Sekunden
CACHE_PAGE_SEO_TTL=300000 # 5 Minuten
CACHE_NAVIGATION_TTL=300000 # 5 Minuten
CACHE_PRODUCTS_TTL=30000 # 30 Sekunden
Cache-Konfiguration
Cache-TTLs können über Environment Variables oder direkt in middlelayer/config/cache.ts konfiguriert werden.
🌐 Endpoints
| Service | URL | Beschreibung |
|---|---|---|
| GraphQL API | http://localhost:4000 |
GraphQL Endpoint & Playground |
| Metrics | http://localhost:9090/metrics |
Prometheus Metrics |
| Health Check | http://localhost:9090/health |
Service Health Status |
| Astro Frontend | http://localhost:4321 |
Frontend Application |
📊 GraphQL Schema
Queries
# Produkte
query {
products(limit: 4) {
id
name
price
originalPrice
promotion {
category
text
}
}
product(id: "prod-123") {
id
name
description
price
}
}
# CMS (mit Locale-Support)
query {
pageSeo(locale: "de") {
title
description
}
page(slug: "/about", locale: "de") {
headline
subheadline
}
pages(locale: "en") {
slug
name
}
navigation(locale: "de") {
links {
name
slug
}
}
}
# Übersetzungen
query {
translations(locale: "de", namespace: "auth") {
locale
translations {
key
value
}
}
}
# Content-System (mit Content-Items)
query {
page(slug: "/components", locale: "de") {
slug
name
headline
row1 {
justifyContent
alignItems
content {
... on HTMLContent {
type
name
html
}
... on MarkdownContent {
type
name
content
alignment
}
... on HeadlineContent {
type
text
tag
align
}
... on ImageContent {
type
name
imageUrl
caption
}
}
}
}
}
🏗️ Architektur
Middlelayer
Der Middlelayer verwendet das Adapter Pattern für flexible Datenquellen:
Frontend (Astro)
↓
GraphQL API (Apollo Server)
↓
Resolvers (mit Error Handling Wrapper)
↓
DataService (Singleton, mit Cache + Metrics)
↓
PageMapper (CMS → Domain Transformation)
↓
DataAdapter (Interface)
↓
┌─────────────┬──────────────┬─────────────┐
│ Mock Adapter│ CMS Adapter │ DB Adapter │
└─────────────┴──────────────┴─────────────┘
Vorteile:
- Einfaches Wechseln zwischen Datenquellen
- Testbarkeit durch Mock-Adapter
- Zentrale Logik im DataService
- Caching auf Service-Ebene
- Type-safe Mapping von CMS zu Domain-Typen
- Konsistentes Error Handling
- Zentralisierte Cache-Keys
- Strategy Pattern für wartbaren Code
Content-System
Das System verwendet eine Zwei-Schichten-Typ-Architektur:
-
CMS-Typen (
types/cms/) - Wie Daten vom CMS kommen*SkeletonWrapper mitcontentTypeIdundfields- Verwendet
ComponentLayout(Alias fürcontentLayout) - Nur für Mapper und Mock-Daten
-
Domain-Typen (
types/c_*.ts) - Wie Daten in der App verwendet werdenc_*Content-Item-Typen mittype-Feld- Verwendet
contentLayoutdirekt - Für GraphQL Schema, Astro Components, etc.
-
PageMapper - Konvertiert CMS-Typen zu Domain-Typen
- Strategy Pattern für wartbaren Code
- Automatische Transformation aller Content-Types
Content-Komponenten:
- HTML, Markdown, Iframe, ImageGallery, Image, Quote, YoutubeVideo, Headline
Frontend
- Astro für Server-Side Rendering
- Alpine.js für Client-Side Interaktivität
- Tailwind CSS für Styling
- GraphQL Client für Datenabfragen
🔧 Entwicklung
NPM Scripts
npm run mock:server # Startet GraphQL Server
npm run dev # Startet Astro Dev Server
npm start # Startet beide (concurrently)
npm run build # Production Build
npm run preview # Preview Production Build
Neuen Adapter hinzufügen
- Implementiere
DataAdapterInterface:
// middlelayer/adapters/myAdapter.ts
import type { DataAdapter } from './interface.js';
import type { Page, Product, PageSeo, Navigation } from '../types/index.js';
export class MyAdapter implements DataAdapter {
async getProducts(limit?: number): Promise<Product[]> {
// Implementierung
}
async getPage(slug: string, locale?: string): Promise<Page | null> {
// Implementierung - muss CMS-Typen zurückgeben
// PageMapper konvertiert automatisch zu Domain-Typen
}
// ... weitere Methoden
}
- In
middlelayer/adapters/config.tsregistrieren:
export function createAdapter(): DataAdapter {
const adapterType = process.env.ADAPTER_TYPE || 'mock';
if (adapterType === 'myAdapter') {
return new MyAdapter();
}
// ...
}
Content-Komponenten hinzufügen
- CMS-Typ erstellen:
middlelayer/types/cms/MyComponent.ts - Domain-Typ erstellen:
middlelayer/types/c_myComponent.ts - Mapper erweitern: In
pageMapper.tsStrategy Map erweitern - GraphQL Schema erweitern: In
schema.tsneuen Type hinzufügen - Astro Component erstellen:
src/components/cms/C_myComponent.astro
📈 Monitoring
Prometheus Metrics
Metriken sind verfügbar unter http://localhost:9090/metrics:
graphql_queries_total- Anzahl der Queriesgraphql_query_duration_seconds- Query-Dauercache_hits_total/cache_misses_total- Cache-Statistikendataservice_calls_total- DataService-Aufrufeerrors_total- Fehler-Anzahl
Logging
Structured Logging mit Winston:
- Console Output (Development)
- JSON Logs (Production)
- Log-Level konfigurierbar
Tracing
Automatische Trace-ID-Generierung für Request-Tracking.
🚀 Deployment
Production Build
npm run build
Docker (Beispiel)
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
CMD ["npm", "start"]
🧪 Testing
# GraphQL Query testen
curl -X POST http://localhost:4000 \
-H "Content-Type: application/json" \
-d '{"query":"{ products(limit: 4) { id name price } }"}'
# Health Check
curl http://localhost:9090/health
# Metrics
curl http://localhost:9090/metrics
📚 Weitere Dokumentation
- Type Structure - Detaillierte Typ-Struktur (CMS vs Domain)
- Authentication - Authentication & Authorization
- Monitoring - Monitoring & Observability
- Redis Setup - Redis Cache Konfiguration
- Improvements - Verbesserungsvorschläge & umgesetzte Änderungen
🐛 Bekannte Probleme
Query Complexity Schema-Realm-Konflikt
Problem: graphql-query-complexity hat einen Schema-Realm-Konflikt mit Apollo Server.
Workaround: Das Plugin überspringt den Check bei Realm-Konflikten automatisch. Funktioniert, aber Complexity-Check wird manchmal übersprungen.
Status: Funktioniert, permanente Lösung in Arbeit.
🌍 Internationalization (i18n)
URL-basierte Locales
Das System unterstützt URL-basierte Locales:
/de- Deutsche Version/en- Englische Version/- Auto-Redirect zu Browser-Locale oder Cookie
Übersetzungen
Architektur:
- Defaults: Fallback-Übersetzungen in
src/lib/i18n/defaults.ts - Middlelayer: Übersetzungen können vom GraphQL-Server geladen werden
- Überschreibungen: Middlelayer-Übersetzungen überschreiben Defaults
Verwendung in React:
import { useI18n } from "../lib/i18n/useI18n.js";
function MyComponent() {
const { t } = useI18n("auth");
return <h1>{t("login.title")}</h1>;
}
Verwendung in Alpine.js:
<div x-data="{ t: window.i18n?.t || ((key) => key) }">
<button x-text="t('nav.login')"></button>
</div>
CMS-Locale-Support
Contentful-Ansatz: Contentful verwendet ein Locale-System, bei dem:
- Jedes Content-Feld lokalisiert werden kann
- Standard-Locale wird definiert (z.B.
en-US) - Fallback-Locales können konfiguriert werden
- API-Aufrufe enthalten
locale-Parameter:fields.headline['en-US']
Unser System:
- GraphQL-Schema unterstützt
locale-Parameter für alle CMS-Queries - Adapter-Interface ist vorbereitet für Locale-Parameter
- Mock-Adapter gibt aktuell alle Locales gleich zurück (TODO: Locale-spezifische Mock-Daten)
- Bei Contentful-Integration: Adapter würde
locale-Parameter an Contentful API weitergeben
Beispiel GraphQL Query:
query {
page(slug: "/about", locale: "de") {
headline # Deutsche Übersetzung
subheadline
}
page(slug: "/about", locale: "en") {
headline # Englische Übersetzung
subheadline
}
}
✨ Kürzlich implementierte Verbesserungen
- ✅ Strategy Pattern im PageMapper (statt 8 if-Statements)
- ✅ Code-Duplikation reduziert in DataService (Helper-Klassen)
- ✅ Konsistentes Error Handling (Wrapper-Funktion für alle Resolver)
- ✅ Zentralisierte Cache-Keys (CacheKeyBuilder)
- ✅ Type Safety verbessert (keine
anyTypes mehr) - ✅ User-Formatierung zentralisiert (Helper-Funktion)
🔮 Roadmap
- Query Complexity Schema-Realm-Problem dauerhaft lösen
- Database Adapter (PostgreSQL, MySQL)
- Headless CMS Adapter (Contentful, Strapi)
- Locale-spezifische Inhalte implementieren
- Fallback-Locales konfigurieren
- Rate Limiting
- Authentication/Authorization ✅ (implementiert)
- GraphQL Subscriptions
- i18n: Weitere Sprachen hinzufügen
- i18n: Locale-spezifische Mock-Daten für CMS
- E2E Tests
🤝 Beitragen
- Fork das Repository
- Erstelle einen Feature Branch (
git checkout -b feature/AmazingFeature) - Committe deine Änderungen (
git commit -m 'Add some AmazingFeature') - Push zum Branch (
git push origin feature/AmazingFeature) - Öffne einen Pull Request
📝 Lizenz
Dieses Projekt ist privat.
👥 Autoren
- Entwickelt mit ❤️ für moderne E-Commerce-Lösungen
Hinweis: Dieses Projekt ist in aktiver Entwicklung. Für Fragen oder Probleme öffne bitte ein Issue.