A production-grade, batch-delivery marketplace built for a campus with a 100-metre altitude problem.
- 🏔️ The Problem Worth Solving
- 🚀 The Solution: Batch & Climb
- 🛠️ Tech Stack
- 📋 Prerequisites
- 🚀 Installation & Setup
- ⚙️ Environment Variables
- 🐳 Docker Usage
- 📜 Available Scripts
- 🤝 Contributing
- 📄 License
NIT Arunachal Pradesh sits on a hillside. The student hostels are approximately 100 metres above the street-level vendors. That's not a metaphor - it's a literal altitude gap that makes food and supply delivery painful for everyone involved.
Before Campus Connect, the workflow looked like this:
- Students placed orders over fragmented WhatsApp messages with no confirmation
- Vendors had no consolidated view of what was needed
- 10 student orders = 10 separate uphill climbs for a single vendor
- There were no delivery guarantees, no OTP confirmation, no paper trail
Every order was an uncoordinated, exhausting, inefficient climb.
Campus Connect's core innovation is the Batch & Climb model - a time-slot batching system that turns chaos into a single, coordinated trip.
- Students order from a vendor before a time-slot cutoff
- Orders group automatically into an open batch for that time slot
- Cutoff hits → a BullMQ Repeatable Job locks the batch and generates a cryptographic 4-digit OTP per order
- Vendor reviews the aggregate order list, packs items, and makes one single trip uphill
- Student discloses OTP → server validates → delivery confirmed atomically
stateDiagram-v2
[*] --> OPEN : Student Adds Items & Places Order
state OPEN {
[*] --> PendingPayment
PendingPayment --> OrderPlaced : Verification Confirmed
note right of OrderPlaced: Order links up directly with the vendor's active open time-slot batch.
}
OPEN --> LOCKED : BullMQ Repeatable Job runs every 60s
note right of LOCKED: Triggered when Cutoff Time < Current Time
state LOCKED {
[*] --> AtomicStateTransition
AtomicStateTransition --> StatusUpdate : Set batch = LOCKED && order = BATCHED
StatusUpdate --> OTPGeneration : Cryptographic 4-Digit OTP Generated Per Order
OTPGeneration --> DispatchNotification : Enqueue SEND_NOTIFICATION -> BullMQ
}
LOCKED --> VENDOR_PICKED_UP : Vendor Verifies Aggregate Items & Begins Climb
VENDOR_PICKED_UP --> ARRIVED : Vendor Arrives at Campus Hub Drop-off Location
state ARRIVED {
[*] --> SecureHandoff
SecureHandoff --> OTP_Validation : Student Discloses Delivery OTP
OTP_Validation --> Success : Match Validated via Server State
}
ARRIVED --> COMPLETED : Order Settled Atomically
COMPLETED --> [*]
%% Error Traps
LOCKED --> CANCELLED : Graceful Timeout / Refund Triggered
OPEN --> CANCELLED : Student Cancel / Missing Stock
CANCELLED --> [*]
%% Global Color Styling
style OPEN fill:#e1f5fe,stroke:#03a9f4,stroke-width:1px
style LOCKED fill:#fff3e0,stroke:#ff9800,stroke-width:1px
style ARRIVED fill:#e8f5e9,stroke:#4caf50,stroke-width:1px
style COMPLETED fill:#eceff1,stroke:#607d8b,stroke-width:2px
The result: 1 trip, N orders, zero chaos.
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.2.6 |
| Language | TypeScript | 6.0.3 |
| UI Components | React + Tailwind CSS v4 + shadcn/ui | 19.2.6 / 4 |
| ORM | Prisma | 7.8.0 |
| Auth | Better Auth (Email + Google OAuth) | 1.6.12 |
| Forms | React Hook Form + Zod | 7 / 4.4.3 |
| Server State | TanStack Query | 5 |
| Background Jobs | BullMQ | 5.77.6 |
| Logging | Pino | 10 |
| PDF Generation | @react-pdf/renderer | 4 |
| Service | Image | Purpose |
|---|---|---|
| PostgreSQL | postgres:18.1-alpine |
Primary database |
| Redis | redis:8.2.1-alpine |
Job queues, Pub/Sub |
| MinIO | minio/minio:RELEASE.2025-09-07 |
S3-compatible object storage |
| Nginx | nginx:1.29-alpine |
Reverse proxy, rate limiting |
For the full architecture deep-dive including system diagrams, technical decisions, data model, and monitoring stack, see ARCHITECTURE.md.
| Requirement | Version | Install |
|---|---|---|
| Node.js | v20+ | nodejs.org |
| pnpm | latest | npm install -g corepack && corepack enable && corepack prepare pnpm@latest --activate |
| Docker | v24+ | docs.docker.com |
| Docker Compose | v2.20+ | Bundled with Docker Desktop |
git clone https://github.com/coding-pundit-nitap/campus-connect.git
cd campus-connectcp .env.example .env
# Edit .env with your local credentialspnpm docker:dev:upThis single command orchestrates the entire development environment in the correct dependency order - no manual wiring required:
graph LR
classDef dbSvc fill:#eaf2f8,stroke:#2980b9,stroke-width:2px;
classDef initSvc fill:#fef9e7,stroke:#f1c40f,stroke-width:2px;
classDef appSvc fill:#ebf5fb,stroke:#3498db,stroke-width:2px;
classDef proxySvc fill:#f4ecf7,stroke:#8e44ad,stroke-width:2px;
subgraph Infrastructure [" 1. Infrastructure Baseline "]
db[("postgres-db")]:::dbSvc
redis[("redis-cache")]:::dbSvc
minio[("minio-s3")]:::dbSvc
end
subgraph Initialization [" 2. Migrations & Seed Tasks "]
migrator["migrator-dev"]:::initSvc
buckets["create-buckets"]:::initSvc
end
subgraph Runtime [" 3. Core Compute Runtimes "]
app["app-dev (Next.js)"]:::appSvc
worker["worker-dev (BullMQ)"]:::appSvc
studio["prisma-studio (:5555)"]:::appSvc
end
subgraph NetworkEdge [" 4. Entry Routing "]
nginx["nginx-dev (Port :80)"]:::proxySvc
end
db -->|Healthy| migrator
redis -->|Healthy| app
redis -->|Healthy| worker
minio -->|Healthy| buckets
migrator -->|Complete| app
buckets -->|Complete| app
app -->|Proxy target| nginx
studio -..->|Optional dev tools| nginx
minio -->|"Media route :9001"| nginx
docker compose exec app-dev pnpm exec tsx prisma/seed-test.tsThis populates your local database with a complete development fixture set: a sample shop, vendor account, student account, product catalogue, time-slot batch configurations, and orders spanning all lifecycle states - giving you a fully functional environment without any manual data entry.
| Service | URL |
|---|---|
| Application | http://localhost |
| MinIO Console | http://localhost:9001 |
| Prisma Studio | http://localhost:5555 |
Copy .env.example to .env and populate the values. The full set of variables:
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_REGION=us-east-1
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_REGION=us-east-1
NEXT_PUBLIC_MINIO_BUCKET=campus-connect
MINIO_ENDPOINT=http://minio:9000
NEXT_PUBLIC_MINIO_ENDPOINT=http://localhost:9000
POSTGRES_USER=connect
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=campus_connect
DATABASE_URL=postgresql://connect:mypassword@db:5432/campus_connect?schema=public&connection_limit=10&pgbouncer=true
DIRECT_URL=postgresql://connect:mypassword@db:5432/campus_connect
REDIS_URL=redis://redis:6379
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
ALERT_EMAIL_FROM=alerts@yourdomain.com
ALERT_EMAIL_TO=admin@yourdomain.com
NOTIFICATION_EMAIL_FROM=notifications@example.com
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=changeme| Command | Description |
|---|---|
pnpm infra:dev |
Start full dev stack |
pnpm infra:dev:down |
Stop and remove all dev containers |
For production, we use a comprehensive Makefile to simplify operations:
| Command | Description |
|---|---|
make up |
Start all production services |
make down |
Stop all production services |
make logs |
Stream logs for all production services |
make update-app |
Pull and update app without downtime |
make backup |
Run a full local backup (DB, MinIO, Redis) |
make backup-offsite |
Run a backup and sync it offsite |
make restore-list |
List available backups |
make restore-latest |
Restore from the most recent backup |
make help |
View all available Make commands |
(Alternatively, you can use pnpm infra:prod and pnpm infra:prod:down for basic compose commands).
| Script | Description |
|---|---|
pnpm dev |
Next.js dev server (outside Docker) |
pnpm build |
Production Next.js build |
pnpm build:worker |
Compile worker TypeScript → dist/workers |
pnpm validate |
typecheck + lint:fix + format (required before PR) |
pnpm typecheck |
tsc --noEmit |
pnpm lint:fix |
ESLint auto-fix |
pnpm format |
Prettier write |
| Script | Description |
|---|---|
pnpm db:migrate |
Create + apply migration (local dev) |
pnpm db:deploy |
Apply existing migrations (CI/prod) |
pnpm db:studio |
Open Prisma Studio locally |
pnpm migrator:run |
Run migrator container in production |
pnpm docker:db:psql |
Interactive psql shell |
make db-shell |
Interactive psql shell in prod (Makefile) |
| Script | Description |
|---|---|
pnpm redis:cli |
Interactive Redis CLI |
pnpm redis:memory |
Memory breakdown |
pnpm redis:keyspace |
Key distribution |
pnpm redis:pubsub:channels |
Active Pub/Sub channels |
pnpm redis:bigkeys |
Find large keys |
pnpm redis:flushall |
| Script | Description |
|---|---|
pnpm cleanup:orphaned-files:dry |
Preview MinIO files with no DB reference |
pnpm cleanup:orphaned-files |
Delete orphaned MinIO files |
pnpm docker:prune |
Clean up unused docker volumes/images |
We'd love your help making Campus Connect better. Here's everything you need to know before opening a PR.
Husky + lint-staged run automatically on every commit. Prettier and ESLint will auto-fix staged files before your commit lands - no manual formatting step needed.
yourname/short-description
Example: aryan/stock-restock-notifications
Important
Never skip a layer. The codebase enforces a strict separation of concerns:
API Routes / Server Actions → Services → Repositories → Prisma
- API Routes / Server Actions handle HTTP/RPC concerns only - no business logic
- Services contain all business logic and orchestration
- Repositories are the only layer that touches Prisma directly
- Prisma is the ORM - never called from routes or services directly
Skipping layers (e.g., calling Prisma from a route) will be flagged in code review.
pnpm docker:db:migrateRun this inside the running dev container whenever you modify the Prisma schema.
pnpm validateThis runs typecheck + lint:fix + format in sequence. PRs that fail validation will not be merged.
Keep PRs under 300 lines changed. Large PRs are hard to review and slow down the team. Break big features into logical, reviewable chunks.
feat: add OTP resend endpoint for locked batches
fix: prevent double-lock race condition in batch closer
docs: update seed script instructions in README
refactor: extract payment verification into service layer
chore: upgrade Prisma to 7.8.0
See CONTRIBUTING.md for the full contribution workflow.
Maintained by Coding Club @ NIT Arunachal Pradesh (Coding Pundit). See LICENSE for details.
Built with ❤️ at NIT Arunachal Pradesh · connect.nitap.ac.in