A self-hosted, Quizlet-like flashcard app where cards can have unlimited sides. Perfect for vocabulary study with multiple representations — e.g. a Chinese character, Pinyin, English translation, and notes all on a single card.
- Unlimited card sides — define as many labeled sides as you want per card
- Study mode — cycles through sides and cards with keyboard navigation (Space / ← →)
- Deck management — create, edit, and delete decks with optional descriptions
- Card editor — add, reorder, or remove sides inline before saving
- Auth — email/password sign up and login; sessions stored in PostgreSQL
| Layer | Technology |
|---|---|
| Frontend + Backend | Next.js 14 (TypeScript, App Router) |
| Styling | Tailwind CSS + shadcn/ui |
| Database | PostgreSQL 16 |
| ORM | Prisma |
| Auth | Auth.js v5 (NextAuth) + Prisma adapter, JWT sessions |
| Reverse Proxy | Nginx |
| Containerization | Docker + Docker Compose |
/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
└── app/ # Next.js project root
├── Dockerfile
├── prisma/
│ └── schema.prisma
└── src/
├── app/
│ ├── (auth)/ # Login / signup pages
│ ├── dashboard/ # User's decks list
│ ├── decks/[id]/ # Deck detail + card management
│ ├── decks/[id]/study/ # Study mode
│ └── api/auth/ # Auth.js route handler
├── components/ # UI components
└── lib/ # Prisma client, auth config, utils
users — id, email, name, password, created_at
decks — id, user_id, name, description, created_at
cards — id, deck_id, position, created_at
card_sides — id, card_id, label, content, position
Each card has N card_sides. Labels and count are user-defined (e.g. "Chinese", "Pinyin", "English", "Notes"). All relations cascade on delete.
# 1. Copy env file and fill in secrets
cp .env.example .env
# 2. Start all services
docker compose up -d
# 3. Run database migrations
docker compose exec app npx prisma migrate dev --name init
# 4. Open the app
open http://localhostCopy .env.example to .env and set the following:
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (uses db hostname inside Docker) |
AUTH_SECRET |
Random 32+ character secret for Auth.js v5 |
AUTH_URL |
App base URL (e.g. http://localhost) |
POSTGRES_USER |
Database username |
POSTGRES_PASSWORD |
Database password |
POSTGRES_DB |
Database name |
NEXTAUTH_SECRET/NEXTAUTH_URLare also accepted for backwards compatibility.
| Service | Image | Exposed |
|---|---|---|
app |
Node 20 / Next.js standalone | internal only |
db |
postgres:16-alpine | internal only |
nginx |
nginx:alpine | port 80 |
The app service is not exposed directly — all traffic goes through Nginx on port 80.