Como Construir um Aplicativo de Flashcards com Next.js e MongoDB

Como Construir um Aplicativo de Flashcards com Next.js e MongoDB

Como Construir um Aplicativo de Flashcards com Next.js e MongoDB

Já passou a noite toda estudando para uma prova? Se sim, sabe bem como é difícil memorizar tanta coisa de uma só vez. Os flashcards são uma das ferramentas de estudo mais eficazes porque usam a técnica de reavaliação ativa — obrigam seu cérebro a buscar as informações em vez de só ler de passagem.

Desenvolver um aplicativo digital de flashcards junta o melhor do desenvolvimento web moderno com princípios da psicologia do aprendizado. Neste guia, vou te mostrar passo a passo como criar um aplicativo completo de flashcards usando Next.js no frontend e backend, e MongoDB para armazenar os dados.

Por que construir um aplicativo de flashcards?

Flashcards digitais têm vantagens significativas sobre os físicos:

- **Portabilidade**: Acesse seus decks de qualquer lugar com internet
- **Repetição espaçada**: Algoritmos podem otimizar os momentos de revisão
- **Suporte a mídia**: Imagens, áudios e formatação rica
- **Acompanhamento de progresso**: Análises detalhadas do seu desempenho

Casos de uso reais

Antes de mergulhar no código, pense nesses cenários em que nosso app brilha:

1. **Estudantes de Medicina**: Criando flashcards detalhados de anatomia com imagens
2. **Pessoas aprendendo idiomas**: Montando decks de vocabulário com áudio de pronúncia
3. **Certificações profissionais**: Organizando conceitos técnicos complexos de forma sistemática

Visão geral da stack

Vamos usar:
- **Next.js 14** (framework React com renderização no servidor)
- **MongoDB Atlas** (banco de dados em nuvem)
- **Mongoose** (modelagem de objetos para MongoDB)
- **Tailwind CSS** (estilização)

Por que essa stack? Next.js cuida tanto do frontend quanto das rotas de API, simplificando muito o deploy. O esquema flexível do MongoDB funciona muito bem com flashcards que podem ter estruturas diferentes.

Configurando o projeto

Primeiro, crie um novo projeto Next.js:

```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 o Tailwind no arquivo `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: [],
}
```

Adicione ao arquivo `app/globals.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Conexão com o banco de dados

Crie o arquivo `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('Por favor, adicione sua URI do MongoDB ao .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
```

Criando o modelo de Flashcard

No arquivo `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)
```

Rotas da API

As rotas de API do Next.js lidam com nossa lógica backend. Crie `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
}
}
```

Componentes frontend

Crie o componente de formulário `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"
/>



)
}
```

Interface de estudo

Crie `components/StudyMode.js` para praticar com reavaliação ativa:

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

Nenhum flashcard disponível



const card = shuffledCards[currentIndex]

return (


Pergunta:


{card.question}




{showAnswer && (

Resposta:


{card.answer}



)}





{currentIndex > 0 && (

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

)}




Cartão {currentIndex + 1} de {shuffledCards.length}



)
}
```

Página principal do aplicativo

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


Aplicativo de Flashcards




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

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



{activeView === 'create' && (



)}

{activeView === 'study' && (


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

{decks.map(deck => (

))}


)}

{activeView === 'study' && }


Seus Decks


{decks.length === 0 ? (

Nenhum deck criado ainda. Crie seu primeiro flashcard acima!


) : (

    {decks.map(deck => (

  • {deck} ({flashcards.filter(c => c.deck === deck).length} cartões)

  • ))}

)}



)
}
```

Considerações para produção

Para ambiente de produção, pense nesses aprimoramentos:

1. **Autenticação**: Adicione contas de usuário com NextAuth.js
2. **Repetição espaçada**: Implemente algoritmos como SM-2
3. **Mídia rica**: Suporte imagens e áudios nos flashcards
4. **Funcionamento offline**: Use service workers para recursos PWA

Faça o deploy na Vercel (otimizada para Next.js) ou outra plataforma similar. Configure seu cluster no MongoDB Atlas e ajuste as variáveis de ambiente.

Conclusão

Este aplicativo de flashcards demonstra conceitos fundamentais enquanto permanece prático. Agora você tem uma base sólida que pode ser expandida conforme suas necessidades. Seja estudando idiomas, se preparando para provas ou memorizando conteúdo profissional, flashcards digitais oferecem uma solução eficaz e personalizável.

Comece simples, itere frequentemente e foque na experiência do usuário. Boa sorte na sua jornada de aprendizado!

Perguntas frequentes

P: Preciso ter experiência prévia com MongoDB?
R: Conhecimento básico ajuda, mas não é obrigatório. O modelo de documentos do MongoDB é bem intuitivo quando você entende estruturas tipo JSON.

P: Posso adicionar imagens aos flashcards?
R: Com certeza! Estenda o schema com URLs de imagens e modifique os componentes do formulário para lidar com uploads de arquivos.

P: Como implemento a repetição espaçada?
R: Armazene o histórico de revisões com timestamps, depois calcule intervalos ótimos baseados no desempenho de recomendação. Bibliotecas como `supermemo` podem te ajudar com os algoritmos.

P: Este app é adequado para dispositivos móveis?
R: Com frameworks CSS responsivos como Tailwind, sim. Considere adicionar recursos PWA para uma experiência mais próxima da nativa.

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment