SSajidur Rahman Sajid
COMPLETE2025Full-stack Engineer

LedgerTurf

Turf booking ecosystem — live on Vercel

ReactViteTailwind CSSRedux ToolkitNode.jsExpressMongoDB AtlasMongooseCloudinaryJWTVercel

[ 01 ] — Context

Problem

  • Turf owners in Dhaka booked slots over phone and WhatsApp — double-booking was routine, availability was never current, and players had no way to compare grounds.
  • Timezones and 24-hour clock confusion made 'is this slot free right now' a genuinely hard query to answer correctly.

[ 02 ] — Response

Solution

LedgerTurf is a monorepo (npm workspaces) with an Express REST API and a React SPA. Players discover turfs on a geo-indexed map, owners publish and manage slots, and admins get full visibility — with JWT auth and role-based access at every layer.

Slots are protected with an overlap check inside a Mongoose transaction session, and availability is computed in UTC with Bangladesh's UTC+6 offset handled explicitly — so 'available now' means the same thing to everyone.

SPA deep links are handled with Vercel rewrite rules so refreshing /turf/:id never 404s.

[ 03 ] — Structure

Architecture

  React SPA (Vite + Redux) ──► /api/v1 ──► Express (JWT bearer)
                                          │  asyncHandler pattern
                                          ├──► Mongo 2dsphere geo index
                                          ├──► Booking overlap check
                                          │    (transaction session)
                                          ├──► Review avg-rating hooks
                                          └──► Cloudinary uploads
  Vercel: frontend + backend, SPA rewrite rules

[ 04 ] — Trade-offs

Key engineering decisions

  1. 01

    GeoJSON 2dsphere index

    Turf locations are stored as GeoJSON and indexed with MongoDB's 2dsphere index for radius-based discovery — the spatial query is commented out in production for Vercel stability but the index and model are in place.

  2. 02

    Explicit UTC+6 handling

    Past-date validation and 'availableNow' both compute against a single UTC clock offset, eliminating the midnight-rollover class of bugs common to BD booking apps.

  3. 03

    Npm workspaces monorepo

    frontend, backend, and an api-shim share the repo so type shapes stay in sync while each package deploys independently to Vercel.

[ 05 ] — Under the hood

Engineering highlights

Overlap check inside a transaction
const session = await mongoose.startSession();
session.startTransaction();
try {
  const clash = await Booking.findOne({
    turf, startTime: { $lt: end },
    endTime: { $gt: start },
  }).session(session);
  if (clash) throw new BookingConflictError();
  await Booking.create([{ turf, start, end }], { session });
  await session.commitTransaction();
} finally { session.endSession(); }
Honest note: overlap validation is session-bound at creation; a unique partial index is the planned hardening step.

[ 06 ] — Outcomes

Results

37
commits
3
roles (player / owner / admin)
2dsphere
geo index on locations
+6
UTC offset handled explicitly

[ 08 ] — Where it's going

Status & next steps

  • Unique partial index on (turf, startTime) to make double-booking impossible at the DB level.
  • Re-enable the geo-search path now that Vercel configuration is stable.