apps/api

Backend API

Node 20 runtime with Hono for routing, Drizzle for persistence on Neon, Stripe for payments, Resend for email, R2 for media. All business rules for bookings and pilgrim counts live here.

Source layout

apps/api/src/
├── index.ts              # entry (serve Hono)
├── app.ts                # mount routes, global error handler
├── routes/
│   ├── packages.route.ts
│   ├── bookings.route.ts
│   ├── payments.route.ts
│   ├── users.route.ts
│   ├── stalls.route.ts
│   ├── water-orders.route.ts
│   ├── reviews.route.ts
│   └── webhooks.route.ts # POST /v1/webhooks/stripe
├── controllers/
├── services/
│   ├── bookings.service.ts
│   ├── payments.service.ts   # Stripe only
│   ├── email.service.ts        # Resend
│   └── storage.service.ts      # R2
├── middleware/
│   ├── auth.middleware.ts
│   ├── cors.middleware.ts
│   ├── rate-limit.middleware.ts
│   └── logger.middleware.ts
└── lib/
    ├── db.ts                 # import from @chalo-kumbh/db
    ├── stripe.ts
    ├── resend.ts
    └── r2.ts

API versioning

Public REST surface under /v1/…. Breaking changes require /v2 or additive fields only with backward compatibility.

Stripe webhooks

  • Verify Stripe-Signature with the webhook secret.
  • Handle events idempotently (e.g. store Stripe event id or payment external id before mutating booking).
  • On success: set payment row + transition booking to confirmed when business rules satisfied.
  • Trigger Resend confirmation + queue WhatsApp send.

Server-enforced business rules

  • Pilgrim details required before confirming paid booking.
  • Pind Pradanam: ancestor records required when package type demands it.
  • Shahi Snan: service date must be one of fixed calendar rows.
  • Group puja: 10–30 pilgrims; personal puja: 1–5.

Environment variables (illustrative)

DATABASE_URL=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
RESEND_API_KEY=
R2_ACCOUNT_ID= R2_ACCESS_KEY_ID= R2_SECRET_ACCESS_KEY= R2_BUCKET=
BETTER_AUTH_SECRET= (and provider-specific vars)
PUBLIC_APP_URL=