Modern, Production-Ready, Bilingual Learning Management System Built with Next.js 15 & PostgreSQL.
Live Demo • Features • Architecture • Tech Stack • Quickstart • Environment Variables • Database • Core Modules • Deployment • Contributing • License
- 🌐 Full Internationalization (i18n): Seamless English & Indonesian bilingual support with instant locale switching via
next-intland dual-language database fields. - ⚡ Next.js 15 & React 19: Powered by Next.js App Router, Server Components, Server Actions, and Turbopack.
- 🔐 Better Auth Authentication: Complete auth suite with Email/Password, Google OAuth, Google One Tap, session management, and Role-Based Access Control (
student,instructor,admin). - 💳 Dual Payment Gateways:
- DOKU (Jokul): Indonesia's leading payment gateway supporting QRIS (GoPay, ShopeePay, DANA, OVO), Virtual Accounts (BCA, Mandiri, BRI, BNI), E-Wallets, and Credit Cards.
- Stripe: International credit/debit card payments with webhooks.
- 📜 Dynamic PDF Certificates: Automated PDF certificate generator with
@react-pdf/rendererfeaturing custom digital signatures and public QR code verification (/v/[id]). - 🎮 Gamification & XP System: Real-time XP point tracking, multi-tier achievement badges, daily learning streaks, and a live global student leaderboard.
- 🛠️ Instructor & Admin Studio: Course creator with rich-text curriculum management, video player integration, interactive quiz builders, and student enrollment management.
- 🎨 Modern Visual Craftsmanship: Dark & Light mode toggle, Tailwind CSS v4, smooth animations, glassmorphism, and responsive mobile navigation.
- ☁️ S3 / Cloudflare R2 Media Storage: Scalable object storage for course thumbnails, avatars, and learning attachments.
ajar/
├── public/ # Static assets, fonts, icons, branding
├── src/
│ ├── actions/ # Next.js Server Actions (Database queries & mutations)
│ │ ├── auth.ts # User authentication & profile actions
│ │ ├── certificate.tsx # Certificate issuance & PDF rendering logic
│ │ ├── course.ts # Public & student course browsing
│ │ ├── curriculum.ts # Modules & lessons CRUD
│ │ ├── payment.ts # Checkout sessions & gateway routing
│ │ ├── quiz.ts # Quiz submission & scoring engine
│ │ └── xp.ts # XP points & badge calculation
│ ├── app/
│ │ ├── [locale]/ # Localized route group (next-intl)
│ │ │ ├── (admin)/ # Admin panel (Users, Categories, Global Courses)
│ │ │ ├── (auth)/ # Sign-in & Sign-up pages
│ │ │ ├── (instructor)/ # Studio workspace (Course creation, Quiz builder)
│ │ │ ├── (public)/ # Landing page, Courses catalog, Legal & Contact
│ │ │ ├── (student)/ # Student dashboard, My Courses, XP, Leaderboard
│ │ │ └── v/[id]/ # Public verifiable certificate viewer
│ │ ├── api/ # API Route Handlers & Webhooks
│ │ │ ├── auth/ # Better-Auth catch-all handler
│ │ │ ├── certificates/ # PDF download endpoint
│ │ │ ├── r2/upload/ # Presigned S3/R2 direct uploads
│ │ │ └── webhooks/ # DOKU & Stripe asynchronous webhooks
│ │ ├── globals.css # Tailwind CSS v4 theme definitions
│ │ └── sitemap.ts # Dynamic XML Sitemap generator
│ ├── components/ # Reusable React UI Components
│ │ ├── admin/ # Admin management tables
│ │ ├── course/ # Course cards, filters, reviews, and progress bars
│ │ ├── layout/ # Navbar, Footer, MobileNav, Sidebar, ThemeToggle
│ │ ├── lesson/ # Video players, Markdown renderers, Quiz engine
│ │ ├── payment/ # Checkout buttons, Payment modals, Resume payment
│ │ ├── studio/ # Course form editors & multilingual inputs
│ │ └── ui/ # Base primitives
│ ├── i18n/ # Internationalization config & JSON translation dictionaries
│ │ ├── messages/ # id.json (Indonesian) & en.json (English)
│ │ ├── navigation.ts # Localized Link, redirect, useRouter, usePathname
│ │ ├── request.ts # Server request i18n resolver
│ │ └── routing.ts # Supported locales & default prefixes
│ └── lib/ # Core utilities & Singletons
│ ├── auth/ # Better Auth server configuration
│ ├── db/ # Drizzle schema definitions, relations, and seeds
│ ├── email/ # Resend transactional mail client
│ ├── payment/ # DOKU Jokul & Stripe integration helpers
│ ├── pdf/ # React-PDF certificate templates
│ ├── r2/ # S3 / Cloudflare R2 bucket client
│ └── env.ts # Zod-validated environment schema
├── drizzle.config.ts # Drizzle Kit CLI configuration
├── next.config.ts # Next.js compiler & domain image patterns
└── package.json # Project dependencies & scripts
| Layer | Technology | Description |
|---|---|---|
| Framework | Next.js 15 (App Router) | Fullstack React framework with Server Actions & Turbopack |
| UI Library | React 19 | Latest React features and concurrency primitives |
| Language | TypeScript 5 | Strictly typed for robust development |
| Styling | Tailwind CSS v4 | Next-generation CSS framework with CSS variables |
| Database & ORM | PostgreSQL (Neon) + Drizzle ORM | Type-safe SQL ORM and serverless database |
| Authentication | Better Auth | Modern auth supporting credentials, OAuth & roles |
| Internationalization | next-intl | Type-safe bilingual routing and messages |
| PDF Generation | @react-pdf/renderer | Declarative PDF rendering on the server |
| Payment Gateways | DOKU (Jokul) & Stripe | Indonesian & International payment integrations |
| Storage | Cloudflare R2 / AWS S3 | S3-compatible object storage via @aws-sdk/client-s3 |
- Node.js: v20.x or higher
- PostgreSQL: PostgreSQL 14+ (Local instance or Cloud provider like Neon)
git clone https://github.com/r-iki/Ajar.git
cd Ajarnpm installCopy the .env.example template into .env.local:
cp .env.example .env.localUpdate .env.local with your database connection URL and Better Auth secret:
NEXT_PUBLIC_APP_URL="http://localhost:3000"
BETTER_AUTH_URL="http://localhost:3000"
BETTER_AUTH_SECRET="your-32-char-random-secret-key"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/ajar_dev?sslmode=disable"# Push schema to database
npm run db:push
# Seed default demo courses, categories, lessons & quizzes
npm run db:seednpm run devOpen http://localhost:3000 in your browser.
| Variable | Required | Description | Default / Example |
|---|---|---|---|
NEXT_PUBLIC_APP_URL |
Yes | Public base URL of your application | http://localhost:3000 |
DATABASE_URL |
Yes | PostgreSQL connection string | postgresql://user:pass@localhost:5432/ajar_dev |
BETTER_AUTH_SECRET |
Yes | Secret key for session signing | openssl rand -base64 32 |
BETTER_AUTH_URL |
Yes | Auth server URL | http://localhost:3000 |
NEXT_PUBLIC_BRAND_NAME |
No | Display brand name | "Ajar LMS" |
NEXT_PUBLIC_SIGNER_NAME |
No | Certificate authority signatory | "Lead Instructor" |
NEXT_PUBLIC_SIGNER_TITLE |
No | Signatory title | "CEO & Founder" |
NEXT_PUBLIC_SUPPORT_EMAIL |
No | Support contact email | "support@example.com" |
NEXT_PUBLIC_SUPPORT_PHONE |
No | Support phone / WhatsApp | "+62 800-0000-0000" |
NEXT_PUBLIC_COMPANY_ADDRESS |
No | Physical company address | "Bandung, Indonesia" |
GOOGLE_CLIENT_ID |
No | Google OAuth Client ID | Optional |
GOOGLE_CLIENT_SECRET |
No | Google OAuth Client Secret | Optional |
NEXT_PUBLIC_GOOGLE_CLIENT_ID |
No | Google One Tap Client ID | Optional |
NEXT_PUBLIC_ENABLE_DOKU |
No | Enable DOKU Payment Gateway | true |
DOKU_CLIENT_ID |
No | DOKU Jokul Client ID | Optional |
DOKU_SECRET_KEY |
No | DOKU Jokul Secret Key | Optional |
DOKU_IS_PRODUCTION |
No | Set DOKU to production mode | false |
NEXT_PUBLIC_ENABLE_STRIPE |
No | Enable Stripe Payment Gateway | false |
STRIPE_SECRET_KEY |
No | Stripe Secret Key | Optional |
STRIPE_WEBHOOK_SECRET |
No | Stripe Webhook Signing Secret | Optional |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
No | Stripe Publishable Key | Optional |
RESEND_API_KEY |
No | Resend API key for transactional mail | Optional |
R2_ENDPOINT |
No | Cloudflare R2 / S3 API endpoint | Optional |
R2_BUCKET_NAME |
No | R2 Bucket Name | Optional |
R2_ACCESS_KEY_ID |
No | R2 Access Key ID | Optional |
R2_SECRET_ACCESS_KEY |
No | R2 Secret Access Key | Optional |
R2_PUBLIC_URL |
No | Public CDN URL for uploaded media | Optional |
Ajar uses Drizzle ORM for migrations, relations, and schema enforcement:
| Command | Description |
|---|---|
npm run db:push |
Directly apply schema changes in src/lib/db/schema.ts to the database |
npm run db:generate |
Generate SQL migration files in ./drizzle folder |
npm run db:migrate |
Run pending migrations against the database |
npm run db:studio |
Open interactive Drizzle Studio database browser at https://local.drizzle.studio |
npm run db:seed |
Seed demo data (courses, instructor, lessons, and quizzes) |
- Static Text: Translated through
src/i18n/messages/id.jsonandsrc/i18n/messages/en.jsonusinggetTranslations(namespace)oruseTranslations(namespace). - Dynamic Database Content: Models store localized text using JSONB or dedicated columns (
title,titleId,description,descriptionId). - Use the
tDb(field, locale)helper (src/lib/i18n/db-helper.ts) to automatically resolve the right language fallback seamlessly.
- Initiation:
src/actions/payment.ts:startCheckout()constructs a payment session with DOKU Jokul or Stripe. - Webhook Handlers:
- DOKU:
/api/webhooks/dokuverifies signature usingDOKU_SECRET_KEYand creates an automaticenrollmentonSUCCESS. - Stripe:
/api/webhooks/stripehandlescheckout.session.completedevents.
- DOKU:
- Template: Built using
@react-pdf/rendererinsrc/lib/pdf/certificate.tsx. - Landscape Layout: Includes course details, student name, digital signature, completion date, and a QR code linking to verification.
- Verification Page: Anyone can scan the QR code to view and verify the certificate at
/[locale]/v/[certificateId].
- Points are awarded for completing lessons, passing quizzes, and finishing entire courses.
- Badges unlock automatically based on milestones (First Step, On Fire, Scholar, Course Master, XP Hunter, Diamond).
- The live leaderboard (
/[locale]/leaderboard) ranks students by total earned XP.
- Push your repository to GitHub.
- Import the project in Vercel Dashboard.
- Set the environment variables listed in
.env.example. - Deploy! Next.js 15 App Router and API routes will configure automatically.
Contributions, bug reports, and feature suggestions are always welcome! Please check out CONTRIBUTING.md to get started.
Created and maintained by Riki Muhammad (@r-iki • rikode.com • ajar.rikode.com).
Distributed under the MIT License. See LICENSE for more details.