Mahlzeitenplan-Generator mit Next.js, OpenAI und PostgreSQL
Tutorial zum Erstellen einer Full-Stack-KI-App, die personalisierte Mahlzeitenpläne generiert. Nutzung von OpenAI-API und Prompt Engineering.
Auf dieser Seite
Haben Sie jemals vor dem Kühlschrank gestanden und sich gefragt: „Was gibt es zum Abendessen?" Entscheidungsmüdigkeit ist real, besonders wenn Sie versuchen, Ihre Gesundheitsziele einzuhalten, Ernährungseinschränkungen zu managen oder aus einem Rezept-Trott auszubrechen. Die Nachfrage nach intelligenter Mahlzeitenplanung steigt, mit einem Marktwert von über 271 Millionen US-Dollar in den USA. Was wäre, wenn Sie ein Tool entwickeln könnten, das dieses Problem mithilfe der KI-Power löst?
In diesem Tutorial werden wir einen personalisierten Mahlzeitenplan-Generator erstellen. Ein Benutzer gibt seine Ernährungsbedürfnisse ein (z.B. „vegan", „Low-Carb"), Einschränkungen („glutenfrei") und Gesundheitsziele („Gewichtsabnahme"), und unsere App generiert einen strukturierten 7-Tage-Mahlzeitenplan.
Wir werden einen modernen, leistungsstarken Tech-Stack verwenden:
- Next.js: Für eine Full-Stack-React-Anwendung mit serverseitigen Funktionen.
- OpenAI API (GPT-4o mini): Um die Mahlzeitenpläne mit intelligenter, menschlicher Kreativität zu generieren.
- PostgreSQL: Eine robuste, quelloffene relationale Datenbank zur Datenspeicherung.
- Prisma: Ein ORM der nächsten Generation, das den Datenbankzugriff zum Kinderspiel macht.
Dieses Projekt ist mehr als nur ein weiterer KI-Chatbot. Es ist eine praktische, realweltliche Anwendung, die Ihnen beibringt, wie Sie KI dazu bringen, strukturierte, zuverlässige Daten zu produzieren – eine entscheidende Fähigkeit für jeden Entwickler, der mit großen Sprachmodellen (LLMs) arbeitet.
Das Problem verstehen
Die Kernherausforderung bei der Verwendung von LLMs für Anwendungen wie diese liegt nicht nur in der Generierung von Text; es geht um die Generierung strukturierter Daten. Ein frei formulierter Absatz, der einen Mahlzeitenplan beschreibt, ist für eine Anwendung schwer zu parsen und anzuzeigen. Wir benötigen ein vorhersagbares JSON-Objekt.
Limitationen einfacher Prompts: Einfach eine KI zu bitten, „einen Mahlzeitenplan im JSON-Format zu erstellen", kann unzuverlässig sein. Das Modell könnte:
- Felder halluzinieren oder inkonsistente Benennung verwenden.
- Vergessen, bestimmte Tage oder Mahlzeiten einzuschließen.
- Die JSON-Syntax brechen, was zu Parsing-Fehlern führt.
Unser Ansatz: Wir werden dies mit fortschrittlichem Prompt-Engineering lösen. Durch die Bereitstellung eines klaren Schemas und spezifischer Anweisungen in unserem Prompt können wir die Zuverlässigkeit der KI-Ausgabe drastisch verbessern. Wir sagen ihr genau, wie unsere JSON-Struktur aussehen soll, und stellen sicher, dass wir saubere Daten erhalten, die wir in unserer PostgreSQL-Datenbank speichern.
Voraussetzungen
Bevor wir beginnen, stellen Sie sicher, dass Sie Folgendes haben:
- Node.js (v18 oder höher) installiert.
- Einen OpenAI API-Schlüssel: Sie erhalten einen auf der OpenAI Platform.
- Eine PostgreSQL-Datenbank: Sie können eine lokal mit Docker betreiben oder einen kostenlosen gehosteten Service wie Supabase oder Neon verwenden.
- Grundlegende Kenntnisse in React, Next.js und JavaScript/TypeScript.
Schritt 1: Einrichten des Next.js-Projekts und Prisma
Zuerst erstellen wir unsere Next.js-Anwendung und richten die Grundlage für unsere Datenbankverbindung ein.
Was wir tun
Wir initialisieren ein neues Next.js-Projekt, installieren Prisma und konfigurieren es für die Verbindung zu unserer PostgreSQL-Datenbank.
Implementierung
-
Next.js-App erstellen: Öffnen Sie Ihr Terminal und führen Sie aus:
bash npx create-next-app@latest ai-meal-planner cd ai-meal-plannerWählen Sie die Standardoptionen, einschließlich TypeScript und dem App Router.
-
Prisma und den PostgreSQL-Treiber installieren:
bash npm install @prisma/client pg npm install --save-dev prismaDas
pg-Paket ist der Node.js-Treiber für PostgreSQL. -
Prisma initialisieren: Dieser Befehl richtet die notwendigen Prisma-Konfigurationsdateien ein.
bash npx prisma initDies erstellt ein
prisma-Verzeichnis mit einerschema.prisma-Datei und einer.env-Datei für Ihre Datenbank-URL. -
Datenbankverbindung konfigurieren: Öffnen Sie die
.env-Datei und setzen Sie dieDATABASE_URL-Variable auf Ihre PostgreSQL-Verbindungszeichenfolge.DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE"Ersetzen Sie die Platzhalter durch Ihre tatsächlichen Datenbankanmeldedaten.
Wie es funktioniert
create-next-app erstellt ein vollständiges Next.js-Projekt. prisma init konfiguriert unser Projekt für die Verwendung von Prisma, indem die Schema-Datei erstellt wird, die als einzige Informationsquelle für unsere Datenbankstruktur dient.
Schritt 2: Definieren des Datenbankschemas
Nun definieren wir die Datenmodelle für unsere Anwendung. Wir brauchen eine Möglichkeit, die für Benutzer generierten Mahlzeitenpläne zu speichern.
Was wir tun
Wir werden die schema.prisma-Datei bearbeiten, um ein MealPlan-Modell zu definieren. Dieses Modell speichert die Eingabe des Benutzers und den generierten JSON-Mahlzeitenplan.
Implementierung
Öffnen Sie prisma/schema.prisma und ersetzen Sie deren Inhalt durch Folgendes:
// Dies ist Ihre Prisma-Schema-Datei,
// mehr dazu erfahren Sie in den Docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model MealPlan {
id String @id @default(uuid())
// Benutzerpräferenzen, die den Plan erstellt haben
preferences String
// Der generierte Mahlzeitenplan, gespeichert als JSON-Objekt
plan Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Wie es funktioniert
generator client: Gibt an, dass wir den Prisma Client generieren möchten, einen typsicheren Query-Builder.datasource db: Konfiguriert unsere Datenbankverbindung und teilt Prisma mit, dass wir PostgreSQL verwenden und wo die Verbindungs-URL zu finden ist.model MealPlan: Definiert unsere Tabelle in der Datenbank.id: Eine eindeutige Kennung für jeden Mahlzeitenplan.preferences: Ein einfacherStringzum Speichern der rohen Benutzereingabe.plan: Das wichtigste Feld. Wir verwenden den nativenJson-Typ, den PostgreSQL unterstützt. Prisma empfiehlt die Verwendung vonJSONBwegen seiner Effizienz und Indexierungsfähigkeiten, und es bildet denJson-Typ aufjsonbin PostgreSQL ab.
Migration ausführen
Um dieses Schema auf unsere Datenbank anzuwenden, führen wir unsere erste Migration aus.
npx prisma migrate dev --name init
Dieser Befehl macht zwei Dinge:
- Erstellt eine SQL-Migrationsdatei, die die Schemaänderungen widerspiegelt.
- Wendet die Migration auf die Datenbank an und erstellt die
MealPlan-Tabelle.
Schritt 3: Erstellen der OpenAI API-Route
Hier passiert die Magie. Wir erstellen eine sichere Backend-API-Route in Next.js, um Anfragen an die OpenAI API zu verarbeiten.
Was wir tun
Wir erstellen eine API-Route (/api/generate), die Benutzerpräferenzen empfängt, einen detaillierten Prompt konstruiert, ihn an OpenAI sendet und dann die strukturierte JSON-Antwort in unserer Datenbank speichert.
Implementierung
-
OpenAI SDK installieren:
bash npm install openai -
Ihren OpenAI API-Schlüssel hinzufügen: Setzen Sie Ihren API-Schlüssel niemals auf der Client-Seite ein. Speichern Sie ihn sicher in Ihrer
.env.local-Datei. Next.js lädt diese Variablen automatisch.# .env.local OPENAI_API_KEY="your-secret-key-here" -
Prisma Client-Instanz erstellen: Aus Effizienzgründen ist es Best Practice, eine einzelne PrismaClient-Instanz zu erstellen und wiederzuverwenden. Erstellen Sie eine Datei unter
lib/prisma.ts:typescript // lib/prisma.ts import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); export default prisma; -
Die API-Route erstellen: Erstellen Sie eine neue Datei unter
app/api/generate/route.ts:typescript // app/api/generate/route.ts import { NextResponse } from 'next/server'; import OpenAI from 'openai'; import prisma from '@/lib/prisma'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); export async function POST(req: Request) { try { const body = await req.json(); const { preferences } = body; if (!preferences) { return new NextResponse('Benutzerpräferenzen sind erforderlich', { status: 400 }); } const prompt = ` Sie sind ein professioneller Ernährungsberater und Mahlzeitenplaner. Erstellen Sie einen personalisierten 7-Tage-Mahlzeitenplan basierend auf den folgenden Benutzerpräferenzen: ${preferences}. Ihre Antwort MUSS ein gültiges JSON-Objekt sein. Fügen Sie keinen Text, keine Notizen oder Erklärungen außerhalb der JSON-Struktur hinzu. Das JSON-Objekt sollte einen einzelnen Wurzel-Schlüssel haben: "mealPlan". "mealPlan" sollte ein Array von 7 Objekten sein, eines für jeden Tag (z.B. "Montag", "Dienstag", ...). Jedes Tagesobjekt muss die folgenden Schlüssel enthalten: "day", "meals" und "dailyTotals". - "day": Der Name des Tages (Zeichenkette). - "meals": Ein Objekt mit drei Schlüsseln: "breakfast", "lunch" und "dinner". - Jede Mahlzeit ("breakfast", "lunch", "dinner") muss ein Objekt mit "name" (Zeichenkette), "ingredients" (Array von Zeichenketten) und "calories" (Zahl) sein. - "dailyTotals": Ein Objekt mit den gesamten "calories" (Zahl) für diesen Tag. Hier ist die erforderliche Struktur: { "mealPlan": [ { "day": "Monday", "meals": { "breakfast": { "name": "Rührei", "ingredients": ["2 Eier", "1/4 Tasse Milch", "Salz", "Pfeffer"], "calories": 250 }, "lunch": { "name": "Gegrillter Hähnchensalat", "ingredients": ["100g Hähnchenbrust", "Gemischter Salat", "Cherrytomaten", "Gurke", "Vinaigrette"], "calories": 400 }, "dinner": { "name": "Lachs mit Quinoa", "ingredients": ["150g Lachsfilet", "1 Tasse Quinoa", "Gedünsteter Brokkoli"], "calories": 550 } }, "dailyTotals": { "calories": 1200 } } ] } `; const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' }, // Dies zwingt das Modell, gültiges JSON auszugeben }); const content = response.choices.message.content; if (!content) { throw new Error('Keine Inhalte von OpenAI zurückgegeben.'); } const mealPlanJson = JSON.parse(content); // In Datenbank speichern const savedPlan = await prisma.mealPlan.create({ data: { preferences: preferences, plan: mealPlanJson, // Prisma übernimmt die JSON-Serialisierung }, }); return NextResponse.json(savedPlan); } catch (error) { console.error('Fehler beim Generieren des Mahlzeitenplans:', error); return new NextResponse('Fehler beim Generieren des Mahlzeitenplans', { status: 500 }); } }
Wie es funktioniert
- Prompt-Engineering: Unser Prompt ist sehr spezifisch. Wir definieren die Persona der KI („professioneller Ernährungsberater"), formulieren das Ziel und vor allem ein strenges Schema für die JSON-Ausgabe. Diese detaillierte Anweisung ist der Schlüssel zu zuverlässigen Daten.
response_format: { type: 'json_object' }: Dies ist eine leistungsstarke Funktion der OpenAI API. Sie zwingt das Modell, ein syntaktisch korrektes JSON-Objekt auszugeben, was die Wahrscheinlichkeit eines Parsing-Fehlers erheblich reduziert.- Sicherheit: Die Route ist ein
POST-Handler, und alle OpenAI-Interaktionen finden auf dem Server statt, wodurch unser API-Schlüssel geschützt bleibt. - Datenbankinteraktion: Nach dem Empfang und Parsen des JSON verwenden wir
prisma.mealPlan.create(), um den Datensatz zu speichern. Prismas Typsicherheit stellt sicher, dass wir Daten einfügen, die unserem Schema entsprechen.
Schritt 4: Aufbau der Frontend-Oberfläche
Nun erstellen wir eine einfache Benutzeroberfläche, damit Benutzer ihre Präferenzen eingeben und den generierten Mahlzeitenplan anzeigen können.
Was wir tun
Wir modifizieren die Hauptseite (app/page.tsx), um ein Formular, Lade- und Fehlerzustände sowie die Anzeige des Mahlzeitenplans in einem lesbaren Format einzubinden.
Implementierung
Ersetzen Sie den Code in app/page.tsx durch Folgendes:
// app/page.tsx
'use client';
import { useState } from 'react';
// Definieren Sie einen Typ für unsere Mahlzeitenplanstruktur für Typsicherheit
type Meal = {
name: string;
ingredients: string[];
calories: number;
};
type DayPlan = {
day: string;
meals: {
breakfast: Meal;
lunch: Meal;
dinner: Meal;
};
dailyTotals: {
calories: number;
};
};
type MealPlanResponse = {
id: string;
preferences: string;
plan: {
mealPlan: DayPlan[];
};
};
export default function HomePage() {
const [preferences, setPreferences] = useState('');
const [mealPlan, setMealPlan] = useState<MealPlanResponse | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
setMealPlan(null);
try {
const response = await fetch('/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ preferences }),
});
if (!response.ok) {
throw new Error(`Fehler: ${response.statusText}`);
}
const data: MealPlanResponse = await response.json();
setMealPlan(data);
} catch (err: any) {
setError(err.message || 'Fehler beim Generieren des Mahlzeitenplans. Bitte versuchen Sie es erneut.');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center p-8">
<h1 className="text-4xl font-bold text-gray-800 mb-4">KI Mahlzeitenplaner 🥗</h1>
<p className="text-lg text-gray-600 mb-8">
Geben Sie Ihre Ernährungsbedürfnisse und Ziele ein, und lassen Sie KI einen Plan für Sie erstellen.
</p>
<form onSubmit={handleSubmit} className="w-full max-w-lg mb-8">
<textarea
value={preferences}
onChange={(e) => setPreferences(e.target.value)}
placeholder="z.B., 2000 Kalorien, proteinreich, glutenfrei, zur Gewichtsabnahme"
className="w-full p-3 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-green-500 focus:border-green-500 transition"
rows={4}
/>
<button
type="submit"
disabled={loading || !preferences}
className="w-full mt-4 bg-green-600 text-white py-3 px-4 rounded-md font-semibold hover:bg-green-700 disabled:bg-gray-400 transition"
>
{loading ? 'Wird generiert...' : 'Mahlzeitenplan generieren'}
</button>
</form>
{error && <p className="text-red-500">{error}</p>}
{mealPlan && (
<div className="w-full max-w-4xl bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-bold text-gray-800 mb-4">Ihr 7-Tage-Mahlzeitenplan</h2>
<p className="mb-4 text-gray-600">Basierend auf Präferenzen: <span className="font-semibold">{mealPlan.preferences}</span></p>
<div className="space-y-6">
{mealPlan.plan.mealPlan.map((day) => (
<div key={day.day} className="border border-gray-200 p-4 rounded-md">
<h3 className="text-xl font-semibold text-green-700">{day.day}</h3>
<p className="text-sm text-gray-500 mb-2">Gesamtkalorien: {day.dailyTotals.calories}</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{Object.entries(day.meals).map(([mealType, meal]) => (
<div key={mealType} className="bg-gray-50 p-3 rounded">
<h4 className="font-bold capitalize">{mealType}</h4>
<p>{meal.name} ({meal.calories} kcal)</p>
<ul className="list-disc list-inside text-sm text-gray-600">
{meal.ingredients.map((ing, i) => <li key={i}>{ing}</li>)}
</ul>
</div>
))}
</div>
</div>
))}
</div>
</div>
)}
</div>
);
}
Wie es funktioniert
'use client': Diese Direktive markiert die Komponente als Client-Komponente und ermöglicht die Verwendung von State und Event-Handlern.- State-Management: Wir verwenden
useState, um Formulareingabe, Ladezustand, Fehler und die endgültigen Mahlzeitenplandaten zu verwalten. - API-Aufruf: Die
handleSubmit-Funktion sendet einePOST-Anfrage an unseren/api/generate-Endpunkt und sendet die Benutzerpräferenzen im Request-Body. - Rendering: Die Komponente rendert bedingt eine Lademeldung, einen Fehler oder den formatierten Mahlzeitenplan basierend auf dem aktuellen Zustand. Wir iterieren über das
mealPlan-Array, um die Details jedes Tages anzuzeigen. - TypeScript: Wir definieren Typen für unsere Daten (
Meal,DayPlan,MealPlanResponse), um Typsicherheit throughout der Komponente zu gewährleisten.
Alles zusammenfügen
Um die Anwendung zu testen, starten Sie den Entwicklungsserver:
npm run dev
Öffnen Sie http://localhost:3000 in Ihrem Browser. Sie sollten das Eingabeformular sehen. Geben Sie Ihre Präferenzen ein, wie z.B. „Ein vegetarischer Plan mit 2200 Kalorien, arm an Natrium, mit schnellen 30-Minuten-Abendessen", und klicken Sie auf „Mahlzeitenplan generieren". Nach einem Moment wird Ihr personalisierter, strukturierter Mahlzeitenplan angezeigt.
Alternative Ansätze
Während unser Ansatz robust ist, gibt es andere Methoden zu berücksichtigen:
- Function Calling: Für komplexere Interaktionen, bei denen die KI möglicherweise externe Tools aufrufen muss (z.B. eine Rezept-API), ist die Function-Calling-Funktion von OpenAI überlegen. Sie ist darauf ausgelegt, strukturiertes JSON zurückzugeben, das direkt auf Funktionsargumente abgebildet wird.
- Zod/Pydantic-Integration: Bibliotheken wie Zod (für TypeScript) können verwendet werden, um Schemas im Code zu definieren. Das OpenAI SDK bietet sogar eingebaute Unterstützung dafür, was die Schema-Definition programmierbarer und wiederverwendbarer machen kann.
Fazit
Wir haben erfolgreich eine vollständige Full-Stack, KI-gestützte Webanwendung von Grund auf erstellt. Sie haben gelernt, wie Sie Next.js mit einer PostgreSQL-Datenbank unter Verwendung von Prisma verbinden, eine sichere API-Route zur Interaktion mit der OpenAI API erstellen und – was am wichtigsten ist – wie Sie Prompts entwickeln, die zuverlässige, strukturierte JSON liefern.
Dieses Projekt ist ein fantastischer Ausgangspunkt. Sie können es jetzt erweitern, indem Sie Features hinzufügen wie:
- Benutzerauthentifizierung zum Speichern von Plänen pro Benutzer.
- Einen „Einkaufslisten"-Generator basierend auf dem Mahlzeitenplan.
- Die Möglichkeit, einzelne Mahlzeiten neu zu generieren oder auszutauschen.
- Detaillierte Nährwertinformationen (Protein, Kohlenhydrate, Fette).
Die Fähigkeit, die Lücke zwischen menschlicher Sprache und strukturierten Daten zu überbrücken, ist eine Superkraft in der modernen Webentwicklung. Jetzt bauen Sie etwas Großartiges!
Ressourcen
- Next.js Dokumentation
- OpenAI API Dokumentation
- Prisma Dokumentation
- PostgreSQL JSON-Typen
- Vercel AI SDK
Kurzübersicht
| Aspekt | Wichtige Informationen |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und -sensibilisierung |
| Maßnahme | Besprechen Sie dies mit Ihrem Arzt |
| Häufigkeit | Wie von Ihrem Betreuungsteam empfohlen |
| Evidenz | Basierend auf aktuellen klinischen Richtlinien |
Häufig gestellte Fragen
F: Woher weiß ich, ob dies auf mich zutrifft? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrem Arzt, um Ihre individuellen Risikofaktoren zu bewerten und geeignete nächste Schritte zu bestimmen.
F: Was sollte ich meinen Arzt fragen? A: Gute Fragen sind: „Was bedeuten meine Ergebnisse?", „Was sind meine Optionen?", „Welche Lebensstiländerungen würden helfen?" und „Wann sollte ich nachuntersuchen?"
F: Gibt es Lebensstiländerungen, die helfen können? A: Die meisten Erkrankungen profitieren von grundlegenden Maßnahmen: ausgewogene Ernährung, regelmäßige körperliche Aktivität (150+ Minuten/Woche), качественный Schlaf (7-9 Stunden), Stressmanagement und Vermeidung von Tabak.
Haftungsausschluss
Dieser Artikel dient nur zu Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie immer einen qualifizierten Arzt, bevor Sie Entscheidungen über Ihre Gesundheit, Behandlung oder Pflege treffen. Individuelle Umstände variieren, und was für eine Person angemessen ist, ist möglicherweise nicht für eine andere geeignet.
Wenn Sie einen medizinischen Notfall haben, rufen Sie sofort 911 (oder Ihre lokale Notrufnummer) an.