Build a Flashcard Study App with Next.js and MongoDB

Build a Flashcard Study App with Next.js and MongoDB

Build a Flashcard Study App with Next.js and MongoDB

If you've ever crammed for an exam the night before, you know how hard it is to remember everything. Flashcards are one of the most effective study tools because they use active recall – forcing your brain to retrieve information rather than passively re-reading it.

Building a digital flashcard app combines modern web development with educational psychology. In this guide, I'll walk you through creating a full-stack flashcard application using Next.js for the frontend/backend and MongoDB for data storage.

Why Build a Flashcard App?

Digital flashcards offer several advantages over physical ones:

- **Portability**: Access your decks anywhere with internet
- **Spaced repetition**: Algorithms can optimize review timing
- **Media support**: Images, audio, and formatting
- **Progress tracking**: Analytics on your learning performance

Real-World Use Cases

Before diving into code, consider these scenarios where our app shines:

1. **Medical Students**: Creating detailed anatomy flashcards with images
2. **Language Learners**: Building vocabulary decks with pronunciation audio
3. **Professional Certification**: Organizing complex technical concepts systematically

Tech Stack Overview

We'll use:
- **Next.js 14** (React framework with server-side rendering)
- **MongoDB Atlas** (cloud database)
- **Mongoose** (MongoDB object modeling)
- **Tailwind CSS** (styling)

Why this stack? Next.js handles both frontend and API routes, simplifying deployment. MongoDB's flexible schema works well for varying flashcard structures.

Setting Up the Project

First, create a new Next.js project:

```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
```

Configure Tailwind in `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: [],
}
```

Add to `app/globals.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Database Connection

Create `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('Please add your MongoDB URI to .env.local')
}

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
```

Creating the Flashcard Model

In `models/Flashcard.js`:

```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 Routes

Next.js API routes handle our backend logic. Create `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 Components

Create a form component `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"
/>



)
}
```

Study Interface

Create `components/StudyMode.js` for active recall practice:

```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

No flashcards available



const card = shuffledCards[currentIndex]

return (


Question:


{card.question}




{showAnswer && (

Answer:


{card.answer}



)}





{currentIndex > 0 && (

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

)}




Card {currentIndex + 1} of {shuffledCards.length}



)
}
```

Main Application Page

Create `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'
}`}
>
Create Flashcards

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



{activeView === 'create' && (



)}

{activeView === 'study' && (


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

{decks.map(deck => (

))}


)}

{activeView === 'study' && }


Your Decks


{decks.length === 0 ? (

No decks yet. Create your first flashcard above!


) : (

    {decks.map(deck => (

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

  • ))}

)}



)
}
```

Deployment Considerations

For production, consider these enhancements:

1. **Authentication**: Add user accounts with NextAuth.js
2. **Spaced Repetition**: Implement algorithms like SM-2
3. **Rich Media**: Support images and audio in flashcards
4. **Offline Support**: Use service workers for PWA capabilities

Deploy to Vercel (optimized for Next.js) or similar platform. Set up MongoDB Atlas cluster and configure environment variables.

Conclusion

This flashcard app demonstrates core concepts while remaining practical. You now have a foundation that can be extended based on specific needs. Whether studying languages, preparing for exams, or memorizing professional material, digital flashcards provide an effective, customizable solution.

Start simple, iterate often, and focus on the core user experience. Good luck with your learning journey!

FAQ

Q: Do I need prior experience with MongoDB?
A: Basic familiarity helps but isn't required. MongoDB's document model is intuitive once you understand JSON-like structures.

Q: Can I add images to flashcards?
A: Yes! Extend the schema with image URLs and modify the form components to handle file uploads.

Q: How do I implement spaced repetition?
A: Store review history with timestamps, then calculate optimal intervals based on recall performance. Libraries like `supermemo` can help with algorithms.

Q: Is this suitable for mobile devices?
A: With responsive CSS frameworks like Tailwind, yes. Consider adding PWA features for native-like experience.

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment