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)

📋 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:

  1. CMS-Typen (types/cms/) - Wie Daten vom CMS kommen

    • *Skeleton Wrapper mit contentTypeId und fields
    • Verwendet ComponentLayout (Alias für contentLayout)
    • Nur für Mapper und Mock-Daten
  2. Domain-Typen (types/c_*.ts) - Wie Daten in der App verwendet werden

    • c_* Content-Item-Typen mit type-Feld
    • Verwendet contentLayout direkt
    • Für GraphQL Schema, Astro Components, etc.
  3. 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

  1. Implementiere DataAdapter Interface:
// 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
}
  1. In middlelayer/adapters/config.ts registrieren:
export function createAdapter(): DataAdapter {
  const adapterType = process.env.ADAPTER_TYPE || 'mock';
  
  if (adapterType === 'myAdapter') {
    return new MyAdapter();
  }
  // ...
}

Content-Komponenten hinzufügen

  1. CMS-Typ erstellen: middlelayer/types/cms/MyComponent.ts
  2. Domain-Typ erstellen: middlelayer/types/c_myComponent.ts
  3. Mapper erweitern: In pageMapper.ts Strategy Map erweitern
  4. GraphQL Schema erweitern: In schema.ts neuen Type hinzufügen
  5. 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 Queries
  • graphql_query_duration_seconds - Query-Dauer
  • cache_hits_total / cache_misses_total - Cache-Statistiken
  • dataservice_calls_total - DataService-Aufrufe
  • errors_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

🐛 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 any Types 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

  1. Fork das Repository
  2. Erstelle einen Feature Branch (git checkout -b feature/AmazingFeature)
  3. Committe deine Änderungen (git commit -m 'Add some AmazingFeature')
  4. Push zum Branch (git push origin feature/AmazingFeature)
  5. Ö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.

Description
No description provided
Readme 244 KiB
Languages
TypeScript 83.6%
Astro 16%
JavaScript 0.4%