Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

15 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation


⚽ TURFI

Real-Time Sports Turf Discovery, Booking & Management Platform


Discover. Book. Play.


MongoDB Express.js React Node.js Socket.io Google Maps Cloudinary Redis Razorpay JWT TailwindCSS Redux


License PRs Welcome Status MERN Stack


πŸ“Œ Table of Contents


🌟 Overview

TURFI is a full-stack, real-time, location-aware sports turf booking platform built on the MERN stack. It bridges the gap between players looking for sports grounds and turf owners managing their facilities β€” creating a digital ecosystem where discovery, booking, communication, and trust all live in one place.

This is not a CRUD app. It is a startup-grade product demonstrating real-world engineering: concurrency handling, event-driven architecture, geospatial queries, real-time sync, payment lifecycles, and multi-role access control.

πŸ‘€ For Players

Search β†’ Discover β†’ Book β†’ Pay β†’ Play

πŸ§‘β€πŸ’Ό For Owners

List Turf β†’ Manage Schedule β†’ Communicate β†’ Earn

πŸ›‘οΈ For Admins

Verify β†’ Moderate β†’ Analyse β†’ Control

🎯 Problem Statement

Pain Point TURFI's Solution
Manual / phone-based bookings Instant online booking with real-time slot availability
No visibility into availability Live WebSocket-powered slot sync β€” no double bookings
Opaque pricing Transparent pricing with filters and comparisons
Fake or unverified listings Admin verification system with trust badges
No centralized discovery Location-based search powered by Google Maps & MongoDB $near
No owner-player communication Built-in real-time chat with typing indicators

🧠 System Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        CLIENT LAYER                              β”‚
β”‚     React.js + Redux Toolkit + TailwindCSS + TanStack Query      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚  REST APIs (JWT Auth)     β”‚  WebSocket (Socket.IO)
                   β–Ό                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     SERVER LAYER (Node.js + Express)             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ Auth Routesβ”‚  β”‚Turf Routes β”‚  β”‚Booking Routesβ”‚  β”‚Chat/WS   β”‚ β”‚
β”‚  β”‚ (JWT/RBAC) β”‚  β”‚(Geo + CRUD)β”‚  β”‚(Pay + Slots) β”‚  β”‚(Socket.IOβ”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
     β”‚                 β”‚                  β”‚                  β”‚
     β–Ό                 β–Ό                  β–Ό                  β–Ό
 MongoDB         Cloudinary           Razorpay           Redis +
 (Geo Index)     (Media CDN)         (Payments)          BullMQ
                                                       (Background
                                                         Workers)
                         β”‚
                         β–Ό
                 Google Maps API
                (Geocoding + Display)

Three-Role System

PLAYER     β†’  Search | Book | Chat | Review | Pay
OWNER      β†’  List | Schedule | Chat | Analytics | Refund
ADMIN      β†’  Verify | Moderate | Dispute | Platform Stats

⚑ Features & Implementation

1. πŸ”„ Real-Time Slot Availability

What it does: Shows live slot availability; prevents double-bookings across concurrent users.

How it's implemented:

  • Socket.IO room-based architecture: each turf has its own socket room (turf:{id})
  • When a user starts the booking flow, a slot lock is acquired (stored in Redis with a TTL of ~5 minutes)
  • All clients in the turf room receive an instant slot:locked or slot:confirmed event
  • If payment fails or session expires, the lock is released and slots re-open in real-time
// Server-side slot locking
io.on("connection", (socket) => {
  socket.on("lock:slot", async ({ turfId, slotId, userId }) => {
    const lockKey = `lock:${turfId}:${slotId}`;
    const locked = await redis.set(lockKey, userId, "EX", 300, "NX");
    if (locked) {
      io.to(`turf:${turfId}`).emit("slot:locked", { slotId });
    } else {
      socket.emit("slot:unavailable", { slotId });
    }
  });
});

Concepts demonstrated: Concurrency control, distributed locking (Redis), real-time event propagation


2. πŸ“ Geo-Based "Near Me" Turf Search

What it does: Finds turfs near the user's current location and sorts by distance, price, and rating.

How it's implemented:

  • Turf locations are stored as GeoJSON Point objects in MongoDB
  • 2dsphere index is created on the location field for performant geospatial queries
  • The frontend requests the user's coordinates via the Geolocation API, then passes them to the backend
  • Google Maps API is used to render turf pins, display routes, and show autocomplete for location search
// MongoDB geospatial query
const turfs = await Turf.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [lng, lat] },
      $maxDistance: radiusInMeters,
    },
  },
  isVerified: true,
}).populate("owner", "name avatar");
// Turf schema with geospatial index
const turfSchema = new Schema({
  location: {
    type: { type: String, default: "Point" },
    coordinates: [Number], // [longitude, latitude]
  },
});
turfSchema.index({ location: "2dsphere" });

Concepts demonstrated: MongoDB geospatial indexing, Google Maps integration, browser Geolocation API


3. πŸ’¬ WebSocket Real-Time Chat

What it does: Enables direct messaging between players and owners for inquiries, negotiation, and support.

How it's implemented:

  • Socket.IO private rooms per conversation (chat:{userId1}:{userId2})
  • Messages are persisted in MongoDB with readStatus, deliveredAt, and seenAt fields
  • Typing indicators broadcast via user:typing and user:stop-typing events with a debounce
  • On reconnect, Socket.IO auto-joins the user back to their active rooms using stored session data
  • Chat history is fetched via REST on initial load; subsequent messages arrive over the socket
socket.on("message:send", async (data) => {
  const message = await Message.create(data);
  io.to(data.roomId).emit("message:receive", message);
});

socket.on("typing:start", ({ roomId }) => {
  socket.to(roomId).emit("user:typing");
});

Concepts demonstrated: Event-driven architecture, persistent socket sessions, message delivery guarantees


4. 🧠 Smart Turf Recommendation Engine

What it does: Surfaces the most relevant turfs based on user context and behavior.

How it's implemented:

  • Rule-based engine using a weighted scoring formula:
score = (1/distance Γ— 0.4) + (avgRating Γ— 0.35) + (priceScore Γ— 0.25)
  • User history (viewed turfs, booked turfs, preferred sports) is tracked in the user document
  • Recommendations are refreshed on each session using an aggregation pipeline
  • Architecture is designed to be swapped with an ML model (collaborative filtering) later without changing the API contract
const recommended = await Turf.aggregate([
  { $geoNear: { near: userLocation, distanceField: "dist.calculated", spherical: true }},
  { $addFields: { score: { $add: [
    { $multiply: [{ $divide: [1, "$dist.calculated"] }, 0.4] },
    { $multiply: ["$avgRating", 0.07] },
  ]}}},
  { $sort: { score: -1 } },
  { $limit: 10 },
]);

Concepts demonstrated: MongoDB aggregation pipelines, scoring algorithms, extensible ML-ready design


5. πŸ’³ Online Payment Integration (Razorpay)

What it does: Handles secure payment collection and confirmation, with refund and invoice support.

How it's implemented:

  • Order creation on the server using Razorpay's Node SDK; order ID returned to frontend
  • Payment verification using HMAC SHA-256 signature validation on the server β€” the booking is only confirmed after signature is verified
  • Webhooks capture asynchronous payment events (success, failure, refund) from Razorpay
  • On successful payment, a booking record is created and the slot lock is promoted to a confirmed booking
  • Invoices are generated as PDFs using pdfkit and stored on Cloudinary
// Server-side payment verification
const generatedSignature = crypto
  .createHmac("sha256", process.env.RAZORPAY_SECRET)
  .update(`${razorpay_order_id}|${razorpay_payment_id}`)
  .digest("hex");

if (generatedSignature === razorpay_signature) {
  await Booking.findByIdAndUpdate(bookingId, { status: "confirmed" });
  io.to(`turf:${turfId}`).emit("slot:confirmed", { slotId });
}

Concepts demonstrated: Payment lifecycle management, HMAC signature verification, webhook handling


6. πŸ”” Notifications & Reminders

What it does: Delivers booking confirmations, upcoming match reminders, and cancellation alerts via in-app and email channels.

How it's implemented:

  • BullMQ job queues (backed by Redis) handle all async notification dispatch
  • A scheduled job runs 2 hours before each booking and sends a reminder
  • In-app notifications are pushed via Socket.IO to the user's session room
  • Email notifications use Nodemailer with HTML templates
// Schedule a reminder job at booking time
await reminderQueue.add(
  "match-reminder",
  { userId, bookingId, turfName, slotTime },
  { delay: slotTime - Date.now() - 2 * 60 * 60 * 1000 } // 2h before
);

// Worker processes the job
reminderWorker.process(async (job) => {
  await sendEmail(job.data);
  io.to(`user:${job.data.userId}`).emit("notification:new", { ... });
});

Concepts demonstrated: Background job queues, event scheduling, multi-channel notification systems


7. βœ… Owner Verification & Admin Approval

What it does: Owners submit documents (ownership proof, facility photos); an admin manually reviews and approves before the listing goes live.

How it's implemented:

  • Documents and facility images are uploaded to Cloudinary via secure signed uploads
  • A verificationStatus field (pending | approved | rejected) controls turf visibility
  • Admins see a dedicated dashboard queue of pending verifications
  • Approved turfs receive a Trust Badge (stored as a boolean flag) visible to all users
// Turf model
verificationStatus: {
  type: String,
  enum: ["pending", "approved", "rejected"],
  default: "pending",
},
documents: [{ url: String, public_id: String, type: String }],
isTrusted: { type: Boolean, default: false },

Concepts demonstrated: Multi-step admin workflows, role-based access control, Cloudinary secure upload


8. ⭐ Reviews, Ratings & Trust System

What it does: Post-booking reviews with star ratings; verified bookings unlock the ability to review.

How it's implemented:

  • Reviews are only permitted after a booking status is completed β€” enforced server-side
  • avgRating and totalReviews are maintained on the Turf document using MongoDB's $avg aggregation and a post-save hook
  • Helpful votes on reviews surface the most useful content
  • Flagging system routes suspicious reviews to admin moderation queue
// Recompute average rating after each review
turfSchema.post("save", async function () {
  const stats = await Review.aggregate([
    { $match: { turf: this._id } },
    { $group: { _id: "$turf", avg: { $avg: "$rating" }, count: { $sum: 1 } }},
  ]);
  await Turf.findByIdAndUpdate(this._id, {
    avgRating: stats[0]?.avg ?? 0,
    totalReviews: stats[0]?.count ?? 0,
  });
});

Concepts demonstrated: Post-save middleware, aggregation pipelines, trust signal systems


9. πŸ“Š Analytics Dashboard

What it does: Gives owners insight into bookings, revenue, and peak hours; gives admins a platform-wide view.

How it's implemented:

  • Owner dashboard: Revenue charts (daily/weekly/monthly), booking count, peak-hour heatmap
  • Admin dashboard: Total platform GMV, new user signups, pending verifications, dispute rate
  • All analytics are computed using MongoDB aggregation pipelines β€” no separate analytics DB required
  • Charts are rendered with Recharts on the frontend; data is pre-aggregated server-side for performance
// Peak hours aggregation
const peakHours = await Booking.aggregate([
  { $match: { turf: turfId, status: "completed" } },
  { $group: { _id: { $hour: "$slotStart" }, count: { $sum: 1 } } },
  { $sort: { count: -1 } },
]);

Concepts demonstrated: Data aggregation, analytics API design, time-series visualization


10. πŸ” Cancellation, Refund & Dispute System

What it does: Handles booking cancellations with configurable refund policies, and a dispute resolution flow for admin.

How it's implemented:

  • Cancellation policy is stored per turf (e.g., full refund > 24h before, 50% within 24h, no refund within 2h)
  • On cancellation, a Razorpay refund is initiated programmatically via the API
  • Disputes (raised when a player is denied entry, or a turf is misrepresented) are submitted with evidence and routed to admin
  • Refund status is tracked and surfaced to the user in real-time via Socket events
const policy = await Turf.findById(turfId).select("cancellationPolicy");
const hoursLeft = (booking.slotStart - Date.now()) / 3600000;
const refundPct = hoursLeft > 24 ? 1 : hoursLeft > 2 ? 0.5 : 0;
const refundAmount = booking.amountPaid * refundPct;

if (refundAmount > 0) {
  await razorpay.payments.refund(booking.paymentId, { amount: refundAmount * 100 });
}

Concepts demonstrated: Business rule engines, payment refund APIs, dispute lifecycle management


🧰 Tech Stack

Layer Technology Purpose
React Frontend React.js + Vite UI framework, fast HMR
Redux State Redux Toolkit + TanStack Query Global state + server state/caching
Tailwind Styling Tailwind CSS Utility-first CSS
Node.js Runtime Node.js JavaScript server runtime
Express Framework Express.js REST API server
MongoDB Database MongoDB + Mongoose Document DB, geospatial indexing
Socket.io Real-Time Socket.IO WebSocket communication
Redis Cache/Queue Redis + BullMQ Slot locking, background jobs
Cloudinary Media Cloudinary Image/document storage & CDN
Google Maps Maps Google Maps API Geocoding, location display, directions
Razorpay Payments Razorpay Payment gateway (INR support)
JWT Auth JWT + bcrypt Stateless authentication

πŸ—οΈ Advanced Concepts Used

βœ… Real-Time Synchronization      β†’  Socket.IO rooms, slot locking via Redis
βœ… Role-Based Access Control      β†’  User | Owner | Admin middleware guards
βœ… Geospatial Querying            β†’  MongoDB 2dsphere index + $near operator
βœ… Event-Driven Architecture      β†’  BullMQ queues for notifications and emails
βœ… Concurrent Booking Prevention  β†’  Distributed Redis lock with TTL
βœ… Secure Authentication          β†’  JWT access + refresh token rotation
βœ… Payment Lifecycle Handling     β†’  Order β†’ Verify β†’ Confirm β†’ Refund
βœ… Scalable API Design            β†’  RESTful, versioned, paginated APIs
βœ… Media Pipeline                 β†’  Cloudinary signed uploads, auto-optimization
βœ… Analytics via Aggregations     β†’  MongoDB pipelines β€” no extra analytics service

πŸ“ Project Structure

turfi/
β”œβ”€β”€ client/                        # React frontend
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/            # Reusable UI components
β”‚   β”‚   β”‚   β”œβ”€β”€ Map/               # Google Maps integration
β”‚   β”‚   β”‚   β”œβ”€β”€ Chat/              # Real-time chat UI
β”‚   β”‚   β”‚   └── Booking/           # Slot picker, payment flow
β”‚   β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”‚   β”œβ”€β”€ User/              # Player-facing pages
β”‚   β”‚   β”‚   β”œβ”€β”€ Owner/             # Owner dashboard
β”‚   β”‚   β”‚   └── Admin/             # Admin panel
β”‚   β”‚   β”œβ”€β”€ store/                 # Redux slices
β”‚   β”‚   β”œβ”€β”€ hooks/                 # Custom React hooks
β”‚   β”‚   └── socket/                # Socket.IO client setup
β”‚   └── package.json
β”‚
β”œβ”€β”€ server/                        # Node.js + Express backend
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ controllers/           # Route handler logic
β”‚   β”‚   β”œβ”€β”€ models/                # Mongoose schemas
β”‚   β”‚   β”‚   β”œβ”€β”€ User.js
β”‚   β”‚   β”‚   β”œβ”€β”€ Turf.js            # GeoJSON location field
β”‚   β”‚   β”‚   β”œβ”€β”€ Booking.js
β”‚   β”‚   β”‚   β”œβ”€β”€ Message.js
β”‚   β”‚   β”‚   └── Review.js
β”‚   β”‚   β”œβ”€β”€ routes/                # Express route definitions
β”‚   β”‚   β”œβ”€β”€ middleware/            # Auth, RBAC, error handlers
β”‚   β”‚   β”œβ”€β”€ socket/                # Socket.IO event handlers
β”‚   β”‚   β”‚   β”œβ”€β”€ chat.js
β”‚   β”‚   β”‚   └── booking.js
β”‚   β”‚   β”œβ”€β”€ queues/                # BullMQ workers & jobs
β”‚   β”‚   β”‚   β”œβ”€β”€ reminderQueue.js
β”‚   β”‚   β”‚   └── emailQueue.js
β”‚   β”‚   β”œβ”€β”€ utils/                 # Helpers (geo, payment, cloudinary)
β”‚   β”‚   └── config/                # DB, Redis, Cloudinary config
β”‚   └── package.json
β”‚
└── README.md

πŸš€ Getting Started

Prerequisites

node >= 18.x
npm >= 9.x
MongoDB Atlas or local MongoDB instance
Redis (local or Upstash)
Razorpay account (test keys)
Google Maps API key
Cloudinary account

Installation

# 1. Clone the repository
git clone https://github.com/yourusername/turfi.git
cd turfi

# 2. Install server dependencies
cd server && npm install

# 3. Install client dependencies
cd ../client && npm install

Running Locally

# Terminal 1 β€” Start Redis
redis-server

# Terminal 2 β€” Start the backend server
cd server
cp .env.example .env   # Fill in your environment variables
npm run dev

# Terminal 3 β€” Start the React frontend
cd client
npm run dev

Open http://localhost:5173 in your browser.


πŸ” Environment Variables

Server (server/.env)

# App
PORT=5000
NODE_ENV=development
CLIENT_URL=http://localhost:5173

# MongoDB
MONGODB_URI=mongodb+srv://<user>:<password>@cluster.mongodb.net/turfi

# JWT
JWT_SECRET=your_jwt_secret_key
JWT_REFRESH_SECRET=your_refresh_secret
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

# Redis
REDIS_URL=redis://localhost:6379

# Cloudinary
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret

# Razorpay
RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxx
RAZORPAY_SECRET=your_razorpay_secret

# Google Maps (server-side geocoding)
GOOGLE_MAPS_API_KEY=your_google_maps_key

# Email (Nodemailer)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your@gmail.com
SMTP_PASS=your_app_password

Client (client/.env)

VITE_API_URL=http://localhost:5000/api
VITE_SOCKET_URL=http://localhost:5000
VITE_GOOGLE_MAPS_API_KEY=your_google_maps_key
VITE_RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxx

πŸ“‘ API Reference

Authentication

Method Endpoint Description
POST /api/auth/register Register new user/owner
POST /api/auth/login Login, returns JWT pair
POST /api/auth/refresh Refresh access token
POST /api/auth/logout Invalidate refresh token

Turfs

Method Endpoint Description
GET /api/turfs?lat=&lng=&radius= Geo-search nearby turfs
GET /api/turfs/:id Get turf details
POST /api/turfs Create turf listing (Owner)
PUT /api/turfs/:id Update turf (Owner)
GET /api/turfs/:id/slots?date= Get available slots

Bookings

Method Endpoint Description
POST /api/bookings Create booking + Razorpay order
POST /api/bookings/verify Verify payment signature
GET /api/bookings/my Get user's bookings
POST /api/bookings/:id/cancel Cancel with refund

Admin

Method Endpoint Description
GET /api/admin/turfs/pending Pending verifications
PUT /api/admin/turfs/:id/verify Approve/reject turf
GET /api/admin/stats Platform analytics
GET /api/admin/disputes All open disputes

πŸ–ΌοΈ Screenshots

(Coming soon β€” UI in active development)

Page Description
πŸ—ΊοΈ Discovery Map Google Maps view with turf pins, filter panel
πŸ“… Slot Picker Calendar with real-time slot availability
πŸ’¬ Chat Window Real-time owner-player messaging
πŸ§‘β€πŸ’Ό Owner Dashboard Revenue charts, booking list, turf management
πŸ›‘οΈ Admin Panel Verification queue, dispute board, platform stats

🀝 Contributing

Contributions are welcome! Please follow these steps:

# 1. Fork the repository
# 2. Create a feature branch
git checkout -b feature/your-feature-name

# 3. Commit changes
git commit -m "feat: add your feature description"

# 4. Push and open a PR
git push origin feature/your-feature-name

Please follow Conventional Commits for commit messages.


πŸ“„ License

This project is licensed under the MIT License β€” see the LICENSE file for details.


Built with ❀️ using the MERN Stack

TURFI β€” Discover. Book. Play.

About

TURFI is a production-style turf reservation system designed to simplify sports ground discovery and booking through real-time slot management, secure login, and multi-role dashboards. It includes user booking flows, owner document verification, and an admin control panel for approving access and maintaining platform trust.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages