Princeton's campus events platform, built by TigerApps.
This is a Turborepo monorepo managed with Bun:
| Package | What it is |
|---|---|
apps/web |
The main app — Next.js 16 (App Router), React 19, Tailwind v4, shadcn/ui |
apps/database |
Shared Drizzle ORM schema + migrations (PostgreSQL) |
packages/inbox-engine |
Git submodule → TigerAppsOrg/InboxEngine, the shared source of truth for Princeton organizations, campus venues and events (MyPrincetonU + listserv emails) |
New to the project? Follow Quick start below — it gets apps/web running locally,
which is the primary thing you need.
Clone with submodules (git clone --recurse-submodules …), or run
git submodule update --init in an existing checkout. InboxEngine is a private repo;
ask a TigerApps admin for access if the submodule fails to fetch.
| Tool | Version | Install |
|---|---|---|
| Bun | ≥ 1.2 | macOS/Linux: curl -fsSL https://bun.sh/install | bash · Windows: powershell -c "irm bun.sh/install.ps1 | iex" |
| Docker Desktop | latest | docker.com (used only for local Postgres) |
| Git | ≥ 2.40 | Pre-installed on macOS; winget install Git.Git on Windows |
| uv | ≥ 0.5 | Only needed for the Python backend — see Python backend |
Windows: use WSL2 or Git Bash for all commands below. Restart your terminal after installing tools so PATH updates apply.
Never use npm, yarn, or pnpm in this repo — always bun.
git clone https://github.com/TigerAppsOrg/TheForum.git
cd TheForum
bun install # installs every workspace package + sets up Husky pre-commit hooksCopy the example files:
cp .env.example .env # root — used by docker-compose
cp apps/web/.env.local.example apps/web/.env.local # Next.js app
cp apps/database/.env.example apps/database/.env # drizzle-kit CLIThen fill in apps/web/.env.local. Env vars are validated at startup by
apps/web/src/env.ts — the app won't boot if a required
one is missing, and that file is the source of truth for what's required.
Can't obtain a value yourself? Ask Ibraheem. He is the contact for all credentials that aren't self-serve (Mapbox tokens, AWS, etc.).
| Variable | Where to get it |
|---|---|
DATABASE_URL |
Default in the example file works as-is with the Docker database (port 5434) |
AUTH_SECRET |
Generate your own: openssl rand -base64 32 |
AUTH_URL |
Optional locally. Required in production: the app's canonical public URL (e.g. https://forum.example.edu) — pins Auth.js callbacks and the CAS service URL to that origin |
AUTH_TRUST_HOST |
Optional. Set to true only when running behind a reverse proxy without AUTH_URL (not needed on Vercel, which Auth.js trusts automatically) |
CAS_BASE_URL |
Optional — defaults to https://fed.princeton.edu/cas/ |
NEXT_PUBLIC_MAPBOX_TOKEN / NEXT_PUBLIC_CAMPUS_MAP_TOKEN / NEXT_PUBLIC_CAMPUS_MAP_STYLE |
Ask Ibraheem — Mapbox tokens + the Princeton campus map style URL |
AWS_S3_BUCKET / AWS_REGION |
Optional (image uploads) — ask Ibraheem if you're working on that feature |
Login uses Princeton CAS — there are no OAuth client credentials to obtain.
Clicking "Log in" goes to /api/auth/cas/login, which redirects to
fed.princeton.edu/cas; CAS sends you back to /api/auth/cas/callback, where the
ticket is validated server-side and your user row is created from your NetID.
Make sure Docker Desktop is running, then from the repo root:
bun run db:up # starts Postgres 17 in Docker (container: the-forum-db, host port 5434)
bun run db:push # push the Drizzle schema into the fresh databaseSanity checks:
docker compose ps # the-forum-db should show "Up (healthy)"
bun run db:logs # tail the Postgres logs if something looks wrongThe database URL is postgresql://forum:forum_password@localhost:5434/the_forum
(also reachable with any Postgres client, e.g. psql, TablePlus, or bun run db:studio).
Optionally fill the database with realistic demo data:
bun run db:seedcd apps/web && bun run devOpen http://localhost:3000. You're set up.
To run the dev server through Turborepo from the repo root: bun run dev.
bun run check # Biome lint + format with auto-fix (run before pushing)
bun run format # format only
bun run build # build all packages
bun run db:up # start Postgres db:down stop it (data persists)
bun run db:push # push schema (dev) db:generate generate SQL migrations
bun run db:migrate # apply migrations db:studio visual DB browser
bun run db:seed # seed demo data (safe to re-run; refuses non-local DBs unless ALLOW_REMOTE_SEED=1)
bun run db:sync-engine # import orgs, venues and events from InboxEngine (needs INBOX_ENGINE_URL/TOKEN)
(cd apps/web && bun test) # unit testsPre-commit hooks (Husky + lint-staged) automatically run Biome on staged files — if your commit fails, read the Biome output, fix, and re-commit.
- Env vars in
apps/web: alwaysimport { env } from "~/env"— neverprocess.env.*directly. New vars get added toapps/web/src/env.tsand the.env.examplefiles. - UI components: use shadcn/ui. Add new ones from
apps/web:bunx shadcn@latest add <component>. - Linting: Biome only (no ESLint/Prettier).
Official organizations (all MyPrincetonU groups, with logos, descriptions, social links and MyPrincetonU page links), campus venues and events come from InboxEngine, which also powers TigerInbox. It ingests MyPrincetonU's official events feed and residential/FreeFood listserv emails, resolves the hosting organization, extracts time and place, and exposes a revisioned change feed.
# apps/database/.env
INBOX_ENGINE_URL=https://inbox-engine.tigerapps.org
INBOX_ENGINE_TOKEN=… # ask a TigerApps admin
bun run db:sync-engine # incremental; add -- --full to replay the whole feedImported orgs have source = 'myprincetonu' and external_id = 'mpu:<group id>'; their
officers are managed on MyPrincetonU. Imported events are owned by the _inboxengine bot user,
carry source (myprincetonu or listserv) and a source_url, and are unpublished (never
deleted) when InboxEngine withdraws them. Production runs the sync every five minutes.
To update the engine version: cd packages/inbox-engine && git pull origin main, then commit
the new submodule pointer.
| Branch | URL | Service (on the-forum-web EC2) |
Database |
|---|---|---|---|
staging |
https://forumdev.tigerapps.org | theforum-staging on :3100 |
theforum_staging (RDS) |
main |
https://forum.tigerapps.org | theforum-production on :3200 |
theforum (RDS) |
Every push to staging or main runs CI, then .github/workflows/deploy.yml:
- Builds a Next.js standalone server with the public map tokens (repo variables) and the
environment's
NEXT_PUBLIC_SITE_URL, and bundlestools/migrate.jsandtools/sync.jswith Bun (deploy/build-release.sh). - Uploads the checksummed tarball to S3 through GitHub OIDC (
TheForumGitHubDeployRole). - Runs the
TheForumDeploySSM document on the instance, which executesdeploy/run-release.sh: fetch the SecureString/theforum/<env>/environment, run migrations, switch thecurrentsymlink, restart systemd, health-check with automatic rollback, install the nginx site, and enable the five-minute InboxEngine sync timer.
nginx serves each host on :80 behind Cloudflare (TLS at the edge); theforumdev.tigerapps.org
redirects to forumdev. InboxEngine runs on the same host (inbox-engine.service, :8300).
To change runtime configuration, update the SSM parameter and redeploy (or re-run the workflow).
main is protected — you cannot push to it directly, and pull requests into main
are only accepted from staging.
- Branch off
main:git checkout -b feat/my-feature origin/main - Open a PR into
stagingand merge it there - When
stagingis ready to ship, open a PR fromstagingintomain
The app crashes on startup with "Invalid environment variables"
A required var in apps/web/.env.local is missing or malformed — compare against
apps/web/.env.local.example and the table above.
ECONNREFUSED / DATABASE_URL errors
The database container isn't running (bun run db:up), or your DATABASE_URL
uses the wrong port — the Docker database listens on 5434, not 5432.
Port 5434 already in use
Change POSTGRES_PORT in the root .env and update DATABASE_URL everywhere to match.
Husky hooks not running
Re-run bun install from the repo root (the prepare script reinstalls hooks).
Wipe the database and start fresh
docker compose down -v (deletes the data volume), then bun run db:up && bun run db:push && bun run db:seed.