Ein kleines Full‑Stack‑Projekt (Next.js + Node.js/Express + MongoDB), das Wetter‑Widgets für frei wählbare Orte bereitstellt. Orte können über eine interaktive 3D‑Erdkugel oder über Textsuche ausgewählt werden. Widgets zeigen die wichtigsten aktuellen Wetterdaten an und lassen sich für Details aufklappen. Geöffnete Widgets werden alle 20 Sekunden aktualisiert.
- Ziele
- Funktionsumfang
- Technischer Stack
- Architektur
- Projektstruktur
- Schnellstart
- API-Referenz (Backend)
- Wetterdaten & Caching
- Geokodierung & Richtlinien
- Frontend-Details
- Entwicklung & Qualität
- Deployment-Hinweise
- Troubleshooting
- Lizenz
- Verständnis für sauberes API‑Design und Trennung von Frontend/Backend
- Integration externer APIs (Open‑Meteo für Wetter, Nominatim/OpenStreetMap für Geokodierung)
- Caching‑Strategien im Backend
- Ein modernes, responsives UI mit interaktiver 3D‑Globus‑Interaktion
- Auswahl von Orten über:
- 3D‑Globus (Klick → nächstgelegene Stadt)
- Textsuche mit Vorschlägen (Autosuggest, nur gültige Treffer)
- Widgets anlegen/löschen (MongoDB persistiert
location+ Metadaten) - Wetterdaten: kompakter Überblick (Temperatur, Wetterlage, Wind), aufklappbar für Details
- Visuelle Temperatur‑Einfärbung pro Widget (Gradient, Intensität via
--widget-tintsteuerbar) - Auto‑Refresh: geöffnete Widgets aktualisieren sich alle 20 Sekunden
- Kein Login erforderlich
- Frontend: Next.js 14, React,
react-globe.gl(Three.js‑basierte Globus‑Komponente), CSS (keine UI‑Lib) - Backend: Node.js (Express/Fastify‑Stil mit Express), Axios/Fetch für externe APIs
- Datenbank: MongoDB (lokal oder Atlas)
- Externe APIs:
- Open‑Meteo (Wetter + Geocoding)
- Nominatim/OpenStreetMap (Reverse‑Geocoding und Vorschläge)
- Caching: In‑Memory‑Cache im Backend (konfigurierbare TTL)
Frontend (Next.js)
├─ Globe (react-globe.gl) ──► Klick: {lat,lng}
│ ▼
│ /geo/reverse (Backend → Nominatim)
│ ▼
│ Ortnamen-Vorschlag
│ ▼
│ Sucheingabe ────────► /geo/suggest (Autosuggest)
│ ▼
│ Widget anlegen ─────► POST /widgets (MongoDB speichert label)
│ ▼
│ Widget anzeigen ───► GET /weather?location=... (Backend:
│ Open‑Meteo Geocoding → Weather API, Cache)
│ ▼
│ Details offen ─────► Poll alle 20 s (nur sichtbare, geöffnete Widgets)
│
└─ Widgets löschen ───► DELETE /widgets/:id
Backend (Express)
├─ /widgets (CRUD)
├─ /weather (Open‑Meteo + Cache)
└─ /geo/* (Nominatim Proxy: suggest + reverse)
/project-root
├── backend/ # Node.js Backend (Express)
│ ├── controllers/
│ │ └── widgetsController.js
│ ├── models/
│ │ └── Widget.js
│ ├── routes/
│ │ └── widgets.js # bindet auch /geo/*, /weather, /health ein
│ ├── services/
│ │ ├── weatherService.js # Open‑Meteo + Caching
│ │ ├── geocodeService.js # Nominatim proxy (suggest/reverse)
│ │ └── cache.js # einfacher In‑Memory‑Cache
│ ├── db.js
│ ├── server.js
│ ├── .env # lokale Variablen (nicht committen)
│ └── .gitignore
├── frontend/ # Next.js Frontend
│ ├── components/
│ │ ├── Globe.jsx
│ │ ├── ThemeToggle.jsx
│ │ └── WeatherWidget.jsx
│ ├── pages/
│ │ ├── _app.js
│ │ └── index.jsx
│ ├── styles/
│ │ ├── global.css
│ │ └── globe.module.css
│ └── utils/
│ ├── api.js
│ ├── useDebounce.js
│ └── useIsMobile.js
└── README.md
- Node.js 18+
- MongoDB lokal (Standardport 27017) oder MongoDB Atlas
- Git, cURL bzw. PowerShell
backend/.env (Beispiel):
PORT=5000
MONGODB_URI=mongodb://localhost:27017/widgets
CACHE_TTL_SECONDS=300
CORS_ORIGIN=http://localhost:3000
# Für Nominatim bitte mit eigener Kennung & Kontaktadresse
APP_USER_AGENT=WeatherApp/0.1 (+you@example.com)
Hinweise:
APP_USER_AGENTist für Nominatim verpflichtend. Verwenden Sie eine echte Kontaktadresse.CACHE_TTL_SECONDSsteuert die serverseitige Wetter‑Cache‑Dauer.
cd backend
npm install
npm run devDer Server läuft standardmäßig unter http://localhost:5000.
Health‑Check (PowerShell):
Invoke-RestMethod http://localhost:5000/healthcd frontend
npm install
npm run devDas Frontend läuft unter http://localhost:3000.
Einfacher Health‑Check.
Beispiel:
Invoke-RestMethod http://localhost:5000/healthGET /widgets– Liste aller gespeicherten Widgets.POST /widgets– Neues Widget anlegen.- Body (JSON):
{ "location": "Berlin, Germany" } - Duplikate werden im Frontend abgefangen und sollten im Backend ebenfalls verhindert werden.
- Body (JSON):
DELETE /widgets/:id– Widget löschen.
PowerShell:
# Anlegen
$body = @{ location = "Berlin, Germany" } | ConvertTo-Json
Invoke-RestMethod http://localhost:5000/widgets -Method Post -Headers @{ "Content-Type" = "application/json" } -Body $body
# Liste
Invoke-RestMethod http://localhost:5000/widgets
# Löschen
Invoke-RestMethod http://localhost:5000/widgets/<ID> -Method DeleteForward‑Geocoding (Proxy auf Nominatim), liefert Vorschläge für die Suchbox.
Query‑Parameter:
q(string, erforderlich): Suchbegrifflimit(optional, Default: 5)
Beispiel:
GET /geo/suggest?q=Stutt&limit=5
Antwort (Beispiel):
[
{ "label": "Stuttgart, Baden-Württemberg, Deutschland", "lat": 48.778, "lon": 9.180 },
{ "label": "Stutensee, Baden-Württemberg, Deutschland", "lat": 49.071, "lon": 8.490 }
]Reverse‑Geocoding (Proxy auf Nominatim), ordnet Klick auf Globus der nächstgelegenen Stadt zu.
Query‑Parameter:
lat(float, erforderlich)lon(float, erforderlich)
Beispiel:
GET /geo/reverse?lat=48.78&lon=9.18
Antwort (Beispiel):
{
"name": "Stuttgart",
"admin1": "Baden-Württemberg",
"country": "Deutschland",
"latitude": 48.778,
"longitude": 9.18
}Kapselt Open‑Meteo (Geocoding + Wetter). Rückgabe enthält aktuelle Werte und Einheiten.
Query‑Parameter:
location(string, erforderlich) – z. B."Berlin, Germany"
Beispiel:
GET /weather?location=Berlin, Germany
Antwort (gekürzt):
{
"current": {
"time": "2025-08-26T15:00",
"temperature_2m": 24.3,
"apparent_temperature": 25.0,
"relative_humidity_2m": 56,
"precipitation": 0.0,
"cloud_cover": 40,
"wind_speed_10m": 3.8,
"wind_gusts_10m": 6.2,
"weather_code": 3
},
"current_units": {
"temperature_2m": "°C",
"apparent_temperature": "°C",
"relative_humidity_2m": "%",
"precipitation": "mm",
"cloud_cover": "%",
"wind_speed_10m": "m/s",
"wind_gusts_10m": "m/s"
}
}- Das Backend cached Wetterantworten pro normalisiertem Ort für
CACHE_TTL_SECONDS(Default 300 s). - Open‑Meteo bietet für viele Orte minütliche/viertelstündliche Aktualität. Der Cache reduziert externe Abrufe.
- Das Frontend pollt nur geöffnete Widgets (Details‑Ansicht) alle 20 Sekunden. Während der TTL liefert das Backend gecachte Werte.
Anpassungen:
- Kürzere TTL für nahezu Live‑Werte
- Längere TTL zur Lastreduktion
- „force“‑Bypass könnte ergänzt werden, ist aber standardmäßig nicht aktiv
- Vorschläge und Reverse‑Geocoding laufen über Nominatim (OpenStreetMap).
- Richtlinien beachten:
- Eigener, aussagekräftiger
User-Agentmit Kontaktadresse (APP_USER_AGENT). - Keine aggressiven Abfragefrequenzen. Die Frontend‑Suche ist auf 250 ms debounced; bei Bedarf serverseitig zusätzlich drosseln.
- Ergebnisse cachen, soweit sinnvoll.
- Eigener, aussagekräftiger
- Für reine Geocoding‑Suche kann alternativ Open‑Meteo Geocoding genutzt werden, für „nächstgelegene Stadt“ liefert Nominatim in der Regel robustere Treffer.
- Globus:
react-globe.glmit transparentem Hintergrund, klickbar; unsere Styles sind kapsuliert instyles/globe.module.css. - Karte unter dem Globus: als Overlay‑Bottom‑Sheet, zentriert, ohne den Globus zu blockieren.
- Widgets:
- Kompaktansicht: Temperatur, Wetterlage, Wind
- Details: 3–5 weitere Werte (gefühlt, Feuchte, Niederschlag, Bewölkung, Böen)
- Temperatur‑Gradient: per HSL gefärbt, Intensität über CSS‑Variable
--widget-tint(Dark/Light‑Modus getrennt).
- Responsive:
- Desktop: linke Spalte (Globus/Suche), rechte Spalte (Widgets). Entkoppelte Höhen, rechte Spalte scrollt separat.
- Mobile: Tabs bzw. Bottom‑Sheet‑Overlay für die Auswahlkarte; große Tap‑Targets.
- Node 18+ empfohlen.
- Empfohlene NPM‑Scripts (falls nicht vorhanden, ergänzen):
- Backend:
dev(nodemon),start - Frontend:
dev,build,start
- Backend:
- Code‑Formatierung mit Prettier optional:
npx prettier --write . .gitignoreenthält:node_modules/, Build‑Artefakte (.next/),.env*, IDE‑Ordner (.idea/,.vscode/).
- Frontend (Next.js):
cd frontend npm run build npm run start # PORT=3000
- Backend (Node/Express):
cd backend npm ci node server.js # oder mit pm2: pm2 start server.js --name weatherapp-api
- CORS:
CORS_ORIGINim Backend auf die produktive Frontend‑URL setzen. - MongoDB: Produktions‑Cluster (Atlas) nutzen, URI per ENV setzen.
- Sicherheit:
.envniemals committen, keine geheimen Keys im Frontend bundlen.
- Backend startet, aber Mongo‑Fehler (ECONNREFUSED 127.0.0.1:27017):
- MongoDB läuft nicht lokal oder Port anders belegt. Entweder Dienst starten oder
MONGODB_URIauf Atlas anpassen.
- MongoDB läuft nicht lokal oder Port anders belegt. Entweder Dienst starten oder
- PowerShell cURL‑Fehler bei
-H "Content-Type: application/json":- In PowerShell
Invoke-RestMethodnutzen:$body = @{ location = "Berlin" } | ConvertTo-Json Invoke-RestMethod http://localhost:5000/widgets -Method Post -Headers @{ "Content-Type" = "application/json" } -Body $body
- In PowerShell
- Klick auf Globus liefert nichts:
- Prüfen, ob das Overlay und der Globus im selben Container liegen (
globe.module.css, Klassencontainer,overlay). - In der Browser‑Konsole sollte bei Klick ein Request auf
/geo/reverse?...erscheinen.
- Prüfen, ob das Overlay und der Globus im selben Container liegen (
- Keine Suchvorschläge:
- Netzwerk‑Tab prüfen (
/geo/suggest?q=...). APP_USER_AGENTin.envkorrekt gesetzt?
- Netzwerk‑Tab prüfen (
- Doppelte Widgets:
- Frontend blockt Duplikate anhand des Labels. Optional im Backend einen Unique‑Index auf
locationsetzen.
- Frontend blockt Duplikate anhand des Labels. Optional im Backend einen Unique‑Index auf
- Polling zu häufig/zu selten:
- Intervall im
WeatherWidget.jsxanpassen (20 s). TTL im Backend ggf. reduzieren/erhöhen.
- Intervall im
Dieses Projekt ist zu Lernzwecken gedacht. Falls eine Lizenz benötigt wird, kann z. B. die MIT‑Lizenz ergänzt werden.