A modern, full-stack quiz application built with Next.js 16, featuring OIDC authentication, real-time leaderboards, and a comprehensive REST API with API key authentication.
This is a Bun workspaces + Turborepo monorepo with:
- apps/web β Main Next.js quiz application
- apps/docs β Documentation app (to be created)
All commands should be run from the repository root using Turborepo.
- π― Quiz Management β Create, edit, and delete quizzes with multiple-choice questions
- β¨ AI Generated Content β Use AI to help generate questions and answers
- π Image Browser β Browse and select images via Unsplash API integration
- π OIDC Authentication β Secure sign-in via OpenID Connect (configurable provider)
- π Role-Based Access β Admin permissions based on OIDC groups claim
- π Leaderboards β Per-quiz and global leaderboards with rankings
- β±οΈ Timed Quizzes β Optional time limits with timeout tracking
- π Randomization β Shuffle questions for each attempt
- π API Keys β Programmatic access with scoped permissions and rate limiting
- π OpenAPI Docs β Interactive API documentation with Scalar
- π Dark Mode β System-aware theme switching
- Monorepo: Bun workspaces + Turborepo
- Framework: Next.js 16 (App Router, Turbopack)
- Runtime: Bun
- Database: PostgreSQL with Drizzle ORM (via bun:sql)
- Cache: Valkey/Redis (optional, via Bun native client)
- Auth: BetterAuth with OIDC + API Key plugins
- UI: shadcn/ui (Base UI - Nova), Lucide Icons
- Validation: Zod
- AI: AI SDK
- Image browser: Unsplash API integration
- Bun >= 1.3.8
- PostgreSQL database
- An OIDC provider (e.g., Keycloak, Auth0, Okta, Pocket ID)
# Clone the repository
git clone <repository-url>
cd quiz-app
# Install dependencies
bun install
# Set up environment variables
cp apps/web/.env.example apps/web/.env.localCreate a .env.local file with the following:
# App URL
NEXT_PUBLIC_APP_URL=http://localhost:3000
# OIDC Configuration
OIDC_ISSUER=https://your-oidc-provider.com
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
# Database (PostgreSQL required)
DATABASE_URL=postgresql://user:password@localhost:5432/quiz_appThe app includes a flexible Role-Based Access Control system. See docs/rbac.md for full documentation.
Quick examples:
# Public quiz platform (guests can browse, must sign in to play)
RBAC_PUBLIC_BROWSE_QUIZZES=true
RBAC_PUBLIC_VIEW_QUIZ=true
RBAC_PUBLIC_LEADERBOARD=true
# Role mappings (OIDC groups β app roles)
RBAC_ROLE_ADMIN_GROUPS=admin,staff
RBAC_ROLE_CREATOR_GROUPS=teachers
# Default role for authenticated users without group mapping
RBAC_DEFAULT_ROLE=user| Variable | Default | Description |
|---|---|---|
RBAC_PUBLIC_BROWSE_QUIZZES |
false |
Allow guests to view quiz list |
RBAC_PUBLIC_VIEW_QUIZ |
false |
Allow guests to view quiz details |
RBAC_PUBLIC_PLAY_QUIZ |
false |
Allow guests to play (results not saved) |
RBAC_PUBLIC_LEADERBOARD |
false |
Allow guests to view leaderboards |
RBAC_DEFAULT_ROLE |
user |
Default role for authenticated users |
RBAC_ROLE_ADMIN_GROUPS |
admin |
OIDC groups that map to admin role |
RBAC_ROLE_MODERATOR_GROUPS |
(empty) | OIDC groups that map to moderator role |
RBAC_ROLE_CREATOR_GROUPS |
(empty) | OIDC groups that map to creator role |
The app uses PostgreSQL with Bun's native SQL driver (bun:sql).
# Start PostgreSQL (via Docker Compose)
docker compose up -d
# Run migrations
bun run db:migrate
# Or push schema directly (development)
bun run db:pushThe app includes an optional Redis/Valkey caching layer to reduce database load for high-traffic deployments. See docs/caching.md for full documentation. Caching is opt-in β if no Redis URL is configured, all queries hit the database directly.
# Start development server
bun --bun run devOpen http://localhost:3000 in your browser.
# Build for production
bun --bun run build
# Start production server
bun --bun run startquiz-app/
βββ app/
β βββ (auth)/ # Authentication pages
β β βββ sign-in/
β βββ (dashboard)/ # Main app pages
β β βββ page.tsx # Quiz list (home)
β β βββ leaderboard/ # Global leaderboard
β β βββ settings/ # Admin API key management
β β βββ quiz/
β β βββ new/ # Create quiz
β β βββ [id]/ # Quiz detail, edit, play, results
β βββ actions/ # Server actions
β βββ api/ # REST API endpoints
β β βββ auth/ # BetterAuth handler
β β βββ leaderboard/ # Global leaderboard
β β βββ quizzes/ # Quiz CRUD + attempts + leaderboards
β βββ docs/ # OpenAPI documentation (Scalar)
βββ components/
β βββ auth/ # Auth components
β βββ layout/ # Header, theme, pagination
β βββ quiz/ # Quiz-related components
β βββ settings/ # API key manager
β βββ ui/ # Reusable UI components
βββ docs/
β βββ rbac.md # RBAC configuration documentation
βββ lib/
βββ auth/ # Auth configuration & helpers
βββ db/ # Database schema & queries
βββ rbac/ # Role-based access control
βββ openapi.ts # OpenAPI 3.1 specification
βββ validations/ # Zod schemas
The Quiz App provides a comprehensive REST API for programmatic access. All endpoints require authentication via API key.
Include your API key in the x-api-key header:
curl -H "x-api-key: your_api_key_here" https://yourapp.com/api/quizzesAdmins can create and manage API keys through the web UI at /settings. Each API key can have specific permission scopes:
| Scope | Description |
|---|---|
quizzes:read |
List and view quizzes, view leaderboards |
quizzes:write |
Create, update, and delete quizzes (requires admin role) |
attempts:read |
View quiz attempts |
attempts:write |
Submit quiz attempts |
API keys are rate-limited to 100 requests per minute by default. When rate-limited, the API returns a 429 Too Many Requests response.
Interactive API documentation is available at /docs powered by Scalar. The documentation includes:
- π Full endpoint reference with request/response schemas
- π§ͺ "Try it" functionality to test endpoints directly in the browser
- π¦ Code snippets in multiple languages (JavaScript, Python, cURL, etc.)
- π Authentication setup for API key configuration
All endpoints return consistent error responses:
{
"error": "Error message describing what went wrong"
}| Status Code | Description |
|---|---|
400 |
Bad Request β Invalid input data |
401 |
Unauthorized β Missing or invalid API key |
403 |
Forbidden β Insufficient permissions |
404 |
Not Found β Resource doesn't exist |
429 |
Too Many Requests β Rate limit exceeded |
500 |
Internal Server Error β Something went wrong |
| Command | Description |
|---|---|
bun --bun run dev |
Start development server |
bun --bun run build |
Build for production |
bun --bun run start |
Start production server |
bun --bun run tsc |
TypeScript type checking |
bun --bun run lint |
Run ESLint |
bun --bun run format |
Format code with Prettier |
bun --bun run format:check |
Check code formatting with Prettier |
bun --bun run stylelint |
Run Stylelint for CSS files |
bun --bun run db:push |
Push schema changes to database |
bun --bun run db:generate |
Generate migration files |
bun --bun run db:migrate |
Run migrations |
bun --bun run db:studio |
Open Drizzle Studio |
bun test |
Run tests |
MIT