MeetCast is a video meeting application built with Next.js, LiveKit, PostgreSQL, and TypeScript.
You schedule a room, share an invite, and join with camera, mic, screen share, chat, and basic moderation. Plans control participant counts and meeting duration. Private rooms restrict join to allowlisted account emails.
Stack: Next.js 16 · React 19 · TypeScript · Tailwind CSS 4 · Drizzle · Neon Postgres · LiveKit · Vitest · pnpm 12
In-app docs (after pnpm dev): open /docs on your local app.
- Create / edit rooms with a start and end time
- Public or private visibility
- Invite links (
inviteCode) - Allowed-email list for private rooms (no invite emails — share the link yourself)
- Participant capacity (plan-driven; SFU hard cap 50)
- Status: waiting / active / ended
- Host can end a meeting from the dashboard
- Camera, microphone, screen share
- Participant grid and speaking state
- Chat, raise hand, reactions (LiveKit data channel)
- Soft LiveKit token refresh before JWT expiry
- Reconnect / disconnect banner
- Host, moderator, participant, guest (guests only on public rooms)
- Mute, disable camera, remove participant
- Moderator promote/demote
- Register / login / logout
- Database sessions (httpOnly cookie)
- Password change, session revoke, account delete
- Soft email verification (login works without verifying)
- Forgot / reset password (Resend optional)
- Free / Starter / Pro / Business (seeded in migrations; admins can edit)
- Limits: max concurrent participants, max room duration
- Checkout → order → payment → idempotent fulfill
- ZarinPal when configured; in-app mock gateway in non-production without a merchant
- Current migration sets plan prices to 0 IRR so checkout still runs without a gateway
- Users, rooms, plans, orders, audit logs
- Bootstrap admins via
ADMIN_EMAILS
Suggested captures: landing, dashboard, rooms, live meeting, plans, private room settings, admin, account settings.
| Technology | Purpose |
|---|---|
| Next.js 16 | App Router UI, Server Actions, API routes |
| React 19 | Application UI |
| TypeScript | Types across app and libs |
| Tailwind CSS 4 | Styling |
| PostgreSQL / Neon | Persistent data |
| Drizzle ORM | Schema + migrations |
| LiveKit | Audio, video, screen share, data channel |
@node-rs/argon2 |
Password hashing (Argon2id) |
| Zod | Validation |
| Vitest | Unit tests |
| Resend (HTTP API) | Optional auth email |
| ZarinPal | Optional paid checkout |
Browser
│
├── Next.js
│ ├── UI
│ ├── Server Actions
│ └── API routes
│
├── PostgreSQL (users, rooms, plans, orders, …)
│
└── LiveKit (media + in-meeting signals)
Next.js owns authentication, room access, plan limits, billing fulfillment, admin actions, and LiveKit token minting.
PostgreSQL stores durable application data. LiveKit is not the database.
LiveKit carries AV and lightweight collaboration. Secrets (LIVEKIT_API_SECRET, AUTH_SECRET, DATABASE_URL) stay server-side.
There is no middleware.ts, no Socket.IO server, and no Redis in this project.
| Topic | What we do |
|---|---|
| LiveKit | SFU + SDKs instead of building WebRTC infrastructure from scratch |
| Drizzle | Typed schema → SQL migrations under drizzle/ |
| No Socket.IO | Chat / raise hand / reactions use LiveKit data channels |
| No Redis | Sessions and rate limits in Postgres for a smaller local stack |
| Capacity | Soft check via listParticipants before token issue; SFU maxParticipants is the hard bound (races possible) |
| Meeting end | Join/token rejected at endTime; ending in Postgres does not instantly force-disconnect LiveKit peers |
| Tokens | Short-lived JWTs from POST /api/livekit/token after authz; identities user:{uuid} or guest:{roomId}:{guestId} |
| Private rooms | Session email must be on room_allowed_emails (host always allowed); guests blocked |
src/
├── app/ # Routes, Server Actions, API handlers
├── components/ # UI (meeting, rooms, billing, admin, docs…)
├── db/ # Drizzle client + schema
└── lib/ # Auth, rooms, LiveKit, payments, security
drizzle/ # SQL migrations
docs/ # Repo markdown (e.g. production-security.md)
- Node.js 20+
- pnpm 12.6 (
packageManagerinpackage.json) - PostgreSQL (Neon pooled URL is the usual setup)
- A reachable LiveKit server (local SFU or LiveKit Cloud)
This repo does not include a production VPS or custom domain.
git clone <repository-url>
cd meetcast
pnpm install
cp .env.example .env.localFill at least:
DATABASE_URL=…
AUTH_SECRET=… # openssl rand -base64 32
LIVEKIT_URL=ws://localhost:7880
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=secretThen:
pnpm db:migrate
pnpm dev- Run a local LiveKit server (docs) or use LiveKit Cloud credentials.
- Keep API key/secret aligned with the server config.
- Register → create a room → open
/room/[roomId]. Use a second browser profile for guest / multi-user tests.
Never put secrets in NEXT_PUBLIC_* variables.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Postgres connection (Neon pooled) |
AUTH_SECRET |
Yes | Session + guest cookie signing (≥32 chars in production) |
LIVEKIT_URL |
Yes | LiveKit WebSocket URL |
LIVEKIT_API_KEY |
Yes | LiveKit API key |
LIVEKIT_API_SECRET |
Yes | LiveKit API secret (server-only) |
NEXT_PUBLIC_APP_URL |
Recommended in prod | Absolute app origin for invites / emails |
ADMIN_EMAILS |
Optional | Comma-separated bootstrap admin emails |
RESEND_API_KEY |
Optional | Auth email; app works without it |
RESEND_FROM_EMAIL |
Optional | Sender; defaults to Resend test address |
ZARINPAL_MERCHANT_ID |
Optional* | Paid checkout |
ZARINPAL_SANDBOX |
Optional | Defaults to sandbox outside production |
PAYMENT_PROVIDER |
Optional | auto | zarinpal | mock |
* Non-zero prices need a provider. Zero-price plans fulfill without a gateway. In non-production, missing ZarinPal merchant → in-app mock at /billing/pay/[orderId].
Full comments: .env.example.
pnpm db:migrate # apply drizzle/*.sql
pnpm db:generate # after schema edits
pnpm db:studio # browse
pnpm db:check # journal sanityDefault plans are inserted by migration (Free 5/60m, Starter 10/120m, Pro 25/240m, Business 50/unlimited duration within the app ceiling). There is no separate seed script.
Do not run destructive reset/drop against production databases.
pnpm dev # Next.js development server
pnpm build # production build
pnpm start # serve production build
pnpm lint
pnpm typecheck
pnpm test # VitestCreate room → validate → check plan limits → save room (+ allowlist) → ready
Invite/room URL → schedule check → visibility/access → capacity → LiveKit token → connect
- Public: invite link + normal schedule/capacity; guests allowed.
- Private: must be logged in; account email on allowlist (or host). No LiveKit token otherwise. No invitation email product.
Plans → checkout → order (amount from plan) → pay or zero-fulfill → verify → fulfillPaidOrder → plan on user
- Argon2id, httpOnly sessions, SameSite cookies
- Same-origin checks on sensitive APIs
- Postgres rate limits (login, register, guest join, tokens, checkout, …)
- Server-only LiveKit secrets; short-lived tokens after authz
- Payment amounts from DB; idempotent fulfillment
- Security headers in
next.config.ts
Operational checklist: docs/production-security.md.
Vitest covers payments, room visibility, schedule, auth email tokens, plans, admin policy helpers, security helpers, settings, and reliability/protocol pieces under src/lib/__tests__/.
pnpm testNo coverage percentage is claimed here.
- No VPS / custom domain / production LiveKit is provisioned by this repository
- Email and paid checkout need external accounts (Resend, ZarinPal) when you want them live
- Soft capacity checks can race; SFU max remains the hard bound
- Ending a room in Postgres does not instantly disconnect LiveKit peers
- Sustained 50-participant load is not benchmarked here
Next.js host + PostgreSQL + LiveKit (WSS, open media ports, usually TURN)
pnpm install --frozen-lockfile
pnpm build && pnpm startSet production env (strong AUTH_SECRET, wss:// LiveKit URL, migrate DB). In-app guide: /docs/deployment.
GET /api/health — safe ok / degraded after a database check.
Built by Milad Joodi · LinkedIn