Eine Flashcard-App mit Next.js und MongoDB bauen

Eine Flashcard-App mit Next.js und MongoDB bauen

Eine Flashcard-App mit Next.js und MongoDB bauen

Wer schon mal in der Nacht vor der Prüfung geblitzgelernt hat, der weiß, wie schwer es ist, alle Fakten im Kopf zu behalten. Flashcards zählen zu den effektivsten Lerthilfen, weil sie das Abrufen von Wissen direkt üben – bekannt als aktives Abrufen –, anstatt nur zu passiv zu lesen.

Eine digitale Flashcard-App zu entwickeln, verbindet moderne Webentwicklung mit Lerntechnologien. In diesem Beitrag zeige ich dir Schritt für Schritt, wie du eine komplette Full-Stack-Anwendung erstellst – mit Next.js für die Benutzeroberfläche und die API, sowie MongoDB für die Datenspeicherung.

Warum eine Flashcard-App bauen?

Digitale Flashcards haben einige Vorteile gegenüber herkömmlichen Karten:

- **Überall nutzbar**: Deine Karten sind immer dabei, wo auch immer du Internet hast
- **Intelligente Wiederholung**: Algorithmen können den Lernplan optimal abstimmen
- **Medienunterstützung**: Bilder, Audio und Formatierungen machen Lernen lebendiger
- **Fortschritts-Tracking**: Analysiere, wie gut du wirklich bist

Praktische Anwendungsfälle

Bevor wir uns an den Code wagen, hier ein paar Beispiele, wo so eine App wirklich hilft:

1. **Medizinstudierende**: Detaillierte Anatomy-Flashcards mit Bildern
2. **Sprachlerner**: Vokabel-Karten mit Aussprache-Audio
3. **Zertifizierungsprüfungen**: Komplexe Fachbegriffe strukturiert üben

Technologie-Stack im Überblick

Wir nutzen:
- **Next.js 14** (React-Framework mit Server-Side-Rendering)
- **MongoDB Atlas** (Cloud-Datenbank)
- **Mongoose** (Objekt-Dokument-Mapper für MongoDB)
- **Tailwind CSS** (Styling-Framework)

Dieser Stack ist besonders praktisch, weil Next.js sowohl die Benutzeroberfläche als auch die API-Routen bereitstellt – das vereinfacht die Bereitstellung enorm. Mongonds flexible Schema passt sich gut an unterschiedliche Flashcard-Formate an.

Projekt einrichten

Zuerst erstellen wir ein neues Next.js-Projekt:

```bash
npx create-next-app@latest flashcard-app
cd flashcard-app
npm install mongoose mongodb
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

Jetzt konfigurieren wir Tailwind in der Datei `tailwind.config.js`:

```javascript
/** @type {import('tailwindcss').tailwind.config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,jsx}",
"./pages/**/*.{js,jsx,ts,jsx}",
"./components/**/*.{js,jsx,ts,jsx}",
],
theme: {
extend: {},
},
plugins: [],
}
```

In der Datei `app/globals.css` fügen wir dann hinzu:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Datenbankverbindung aufbauen

Erstelle die Datei `lib/mongodb.js`:

```javascript
import { MongoClient } from 'mongodb'

const uri = process.env.MONGODB_URI
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
}

let client
let clientPromise

if (!process.env.MONGODB_URI) {
throw new Error('Bitte füge deine MongoDB URI zu .env.local hinzu')
}

if (process.env.NODE_ENV === 'development') {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
client = new MongoClient(uri, options)
clientPromise = client.connect()
}

export default clientPromise
```

Flashcard-Modell erstellen

In der Datei `models/Flashcard.js` definieren wir das Datenmodell:

```javascript
import mongoose from 'mongoose'

const flashcardSchema = new mongoose.Schema({
question: {
type: String,
required: true,
},
answer: {
type: String,
required: true,
},
deck: {
type: String,
required: true,
},
tags: [String],
createdAt: {
type: Date,
default: Date.now,
},
})

export default mongoose.models.Flashcard || mongoose.model('Flashcard', flashcardSchema)
```

API-Routen einrichten

Next.js-API-Routen übernehmen die Logik für unseren Backend. Erstelle `pages/api/flashcards/index.js`:

```javascript
import dbConnect from '../../../lib/dbConnect'
import Flashcard from '../../../models/Flashcard'

export default async function handler(req, res) {
const { method } = req

await dbConnect()

switch (method) {
case 'GET':
try {
const flashcards = await Flashcard.find({})
res.status(200).json(flashcards)
} catch (error) {
res.status(400).json({ success: false })
}
break
case 'POST':
try {
const flashcard = await Flashcard.create(req.body)
res.status(201).json(flashcard)
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
}
```

Frontend-Komponenten bauen

Erstelle das Formular `components/FlashcardForm.js`:

```javascript
import { useState } from 'react'

export default function FlashcardForm({ onSubmit }) {
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const [deck, setDeck] = useState('')

const handleSubmit = async (e) => {
e.preventDefault()
const response = await fetch('/api/flashcards', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, answer, deck })
})

if (response.ok) {
const newFlashcard = await response.json()
onSubmit(newFlashcard)
setQuestion('')
setAnswer('')
setDeck('')
}
}

return (



value={question}
onChange={(e) => setQuestion(e.target.value)}
required
className="w-full p-2 border rounded"
/>



value={answer}
onChange={(e) => setAnswer(e.target.value)}
required
className="w-full p-2 border rounded"
/>



type="text"
value={deck}
onChange={(e) => setDeck(e.target.value)}
required
className="w-full p-2 border rounded"
/>



)
}
```

Lernschnittstelle gestalten

Erstelle `components/StudyMode.js` für das aktive Üben:

```javascript
import { useState, useEffect } from 'react'

export default function StudyMode({ flashcards }) {
const [currentIndex, setCurrentIndex] = useState(0)
const [showAnswer, setShowAnswer] = useState(false)
const [shuffledCards, setShuffledCards] = useState([])

useEffect(() => {
setShuffledCards([...flashcards].sort(() => Math.random() - 0.5))
}, [flashcards])

const nextCard = () => {
setShowAnswer(false)
if (currentIndex < shuffledCards.length - 1) {
setCurrentIndex(currentIndex + 1)
}
}

const prevCard = () => {
setShowAnswer(false)
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1)
}
}

if (!shuffledCards.length) return

Keine Flashcards verfügbar



const card = shuffledCards[currentIndex]

return (


Frage:


{card.question}




{showAnswer && (

Antwort:


{card.answer}



)}





{currentIndex > 0 && (

)}
{currentIndex < shuffledCards.length - 1 && (

)}




Karte {currentIndex + 1} von {shuffledCards.length}



)
}
```

Hauptseite der Anwendung

Erstelle die Datei `app/page.js`:

```javascript
import { useState, useEffect } from 'react'
import FlashcardForm from '../components/FlashcardForm'
import StudyMode from '../components/StudyMode'

export default function Home() {
const [flashcards, setFlashcards] = useState([])
const [activeView, setActiveView] = useState('create')
const [selectedDeck, setSelectedDeck] = useState('')

useEffect(() => {
fetchFlashcards()
}, [])

const fetchFlashcards = async () => {
const response = await fetch('/api/flashcards')
const data = await response.json()
setFlashcards(data)
}

const addFlashcard = (newFlashcard) => {
setFlashcards([newFlashcard, ...flashcards])
}

const filteredCards = selectedDeck
? flashcards.filter(card => card.deck === selectedDeck)
: flashcards

const decks = [...new Set(flashcards.map(card => card.deck))]

return (


Flashcard Study App




onClick={() => setActiveView('create')}
className={`px-4 py-2 mr-2 rounded ${
activeView === 'create' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
>
Flashcards erstellen

onClick={() => setActiveView('study')}
className={`px-4 py-2 rounded ${
activeView === 'study' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
disabled={flashcards.length === 0}
>
Lernsession starten



{activeView === 'create' && (



)}

{activeView === 'study' && (


value={selectedDeck}
onChange={(e) => setSelectedDeck(e.target.value)}
className="w-full p-2 border rounded"
>

{decks.map(deck => (

))}


)}

{activeView === 'study' && }


Deine Karteien


{decks.length === 0 ? (

Keine Karteien vorhanden. Erstelle deine erste Flashcard oben!


) : (

    {decks.map(deck => (

  • {deck} ({flashcards.filter(c => c.deck === deck).length} Karten)

  • ))}

)}



)
}
```

Bereitstellung im Produktivbetrieb

Für den produktiven Einsatz solltest du diese Erweiterungen in Betracht ziehen:

1. **Benutzerauthentifizierung**: Nutzerkonten mit NextAuth.js hinzufügen
2. **Spaced Repetition**: Algorithmen wie SM-2 implementieren
3. **Reichhaltige Medien**: Bilder und Audio in Flashcards unterstützen
4. **Offline-Nutzung**: Service-Worker für PWA-Funktionalität

Die Bereitstellung erfolgt am besten auf Vercel (speziell optimiert für Next.js) oder einer ähnlichen Plattform. Richte einen MongoDB Atlas-Cluster ein und konfiguriere die Umgebungsvariablen entsprechend.

Fazit

Diese Flashcard-App zeigt die Grundlagen, bleibt aber praktisch anwendbar. Du hast jetzt eine solide Grundlage, die du nach Belieben erweitern kannst. Ob beim Sprachenlernen, bei Prüfungsvorbereitungen oder beim Pauschalisieren von Fachthemen – digitale Flashcards bieten eine effektive und anpassbare Lösung.

Beginne einfach, verbessere nach und konzentriere dich stets auf das Wesentliche: das Lernerlebnis. Viel Erfolg bei deiner nächsten Lernphase!

Häufige Fragen

F: Benötige ich Vorkenntnisse in MongoDB?
A: Grundlegende Kenntnisse helfen, sind aber nicht zwingend erforderlich. Mongonds Dokumentenmodell ist recht intuitiv, sobald du das JSON-ähnliche Strukturverständnis hast.

F: Kann ich Bilder zu Flashcards hinzufügen?
A: Ja! Erweitere das Schema um Bild-URLs und passe die Formular-Komponenten an, um Datei-Uploads zu verarbeiten.

F: Wie implementiere ich spaced repetition?
A: Speichere Lernprotokolle mit Zeitstempeln und berechne danach die optimalen Wiederholungsabstände basierend auf der Erinnerungsleistung. Bibliotheken wie `supermemo` unterstützen dich bei den Algorithmen.

F: Ist diese App auch für mobile Geräte geeignet?
A: Mit responsiven CSS-Frameworks wie Tailwind, auf jeden Fall. Denke zusätzlich an PWA-Features, um ein möglichst native Erfahrung zu bieten.

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment