← India Hidden GemsEngineering deep-dive

This is what sits behind a travel platform
that looks like a simple website.

How the platform is built, and run—continuing the story from the case study: how the code is shaped, how it ships, and where the trade-offs are.

Shape

Two-app monorepo: Next.js frontend, one Go API

Data

MongoDB, one collection per domain

Delivery

Terraform + GitHub Actions, keyless via WIF

Environments

Identical staging and production on Google Cloud

System shape 01

USERSGCP HTTPS LOAD BALANCERNGINX · VMNEXT.JSGO APIFIREBASEMONGODBCLOUD STORAGERAZORPAY/*/api/*ssr fetchsign-inverify tokenreads/writesimage uploadscheckout
Edge routing splits by path prefix, but the Go API is the only thing that ever writes to MongoDB, Cloud Storage, or Razorpay—so business logic has exactly one home no matter which route a request came in on.

One API owns every business rule.

nginx routes each request at the edge: everything under “/api” goes to the Go API with that prefix stripped, everything else renders through Next.js. Server-rendered pages call the API directly over the internal network for SSR; the browser calls it again through nginx for anything client-side—cart actions, auth, admin work. Either way it's the same API, so there's exactly one place business logic can live.

Next.js App RouterGo + gorilla/muxMongoDBFirebase AuthGoogle Cloud StorageRazorpay

Module map 02

Grouped by what changes together

Catalog (experiences, trips, handicrafts, boutique stays), editorial (blogs, comments, engagement, testimonials), and commerce (cart, orders, payment, coupons, invoicing) are separate concerns that occasionally talk to each other—an order triggers a notification, a comment triggers engagement scoring—through small interfaces rather than shared internals.

The five-file pattern

Every domain package is shaped the same way: a model with its schema as struct tags, a repository interface, a MongoDB implementation, HTTP controllers, and a route registrar. A new feature is a new package built to that shape, not a redesign of an existing one—so the cost of adding one stays roughly constant as the catalog grows.

Delivery pipeline 03

Staging and production are the same stack, twice.

Each environment is a separate Google Cloud project running an identical Terraform-defined stack: an HTTPS load balancer in front of a regional managed instance group, built from a golden VM image running nginx, the frontend, and the API in Docker Compose.

A push to the main branch triggers GitHub Actions, which authenticates to Google Cloud over Workload Identity Federation—no service-account key ever leaves GCP—builds and pushes both container images, applies Terraform against the compute module with the new image tag, and lets the instance group roll the change out with zero-downtime surge replacement. A health-check smoke test against the load balancer closes the loop; production deploys wait on manual approval.

The credential design behind this, in full ↗

Infrastructure & security boundary 04

VPC · ASIA-SOUTH1 · PRIVATE SUBNET, NO EXTERNAL IPSINTERNETGLOBAL HTTPS LBCI/CD · GITHUB ACTIONSENGINEERIAP TUNNELREGIONAL MIGCLOUD NATSECRET MANAGERARTIFACT REGISTRYMONITORINGEMAIL ALERTforwards to migoutbound egress, nat onlyssh :22, iap-onlypush imageterraform applyreads secretspulls imagecpu / mem5xx · uptime
Every VM is private—no external IP, outbound internet only through Cloud NAT, SSH only through an IAP tunnel. A separate service account handles each concern (compute, CI/CD, Terraform), and Cloud Monitoring watches the whole thing and emails when it trips.

No public attack surface

Firewall rules deny all ingress by default; the only exceptions are the load balancer's health check and an IAP tunnel scoped to a named identity. There is no public SSH port to scan, brute-force, or forget to close.

Watched, not just deployed

CPU, memory, load-balancer 5xx rate, uptime, and application error-rate each have an alert policy wired to email—so a bad deploy or a resource leak surfaces as a notification, not as a customer complaint.

VPC + Cloud NATIdentity-Aware ProxyShielded VMSecret ManagerArtifact RegistryCloud Monitoring

Reliability under real payments 05

Gateway-gated orders

An order is created in a “pending” state before Razorpay is involved, confirmed once by the client-side verification call, and confirmed again by an asynchronous, signature-verified webhook—so a dropped connection on the customer's side doesn't leave payment and order state disagreeing.

Trade-off: two confirmation paths to keep in sync, not one.
Idempotent webhook handling

Razorpay may redeliver the same webhook more than once. Handlers check the order's current status before mutating it, so re-processing a payment that's already marked “paid” is a no-op rather than a double-counted coupon or a duplicate confirmation email.

Trade-off: correctness depends on every handler checking status first.
Honest about the gap

The full checkout sequence—create order, charge, increment coupon usage, send confirmation, generate the invoice—isn't wrapped in a database transaction. A crash mid-sequence is recoverable (the webhook redelivers, state can be reconciled by hand) but not automatic.

Trade-off: simpler code today, in exchange for a manual step in a rare failure case.

Tech stack 06

Backend

Go, gorilla/mux, MongoDB, Firebase Admin SDK for token verification, a hand-rolled Razorpay client, zap for structured logging, and GCP Secret Manager for configuration in every deployed environment.

Frontend

Next.js App Router on React, TypeScript throughout, Tailwind with CSS-variable theme tokens, Zustand for client state, and the Firebase JS SDK for sign-in. Catalog and editorial pages render server-side for search discoverability; cart, checkout, and admin are client-rendered.

How idempotent retries are designed, more generally ↗

Deliberately scoped for now 07

Flat packages, not hexagonal layering.

One generic Mongo repository, not typed per domain.

No message queue—notifications run in the request path.

Chat state lives in memory until it needs Redis.

No cache layer, added only against a measured hot path.

Want the system behind your product? 08

Let's build it to
actually run.

Architecture, delivery pipelines, and the trade-offs that come with running real payments in production—happy to talk through what your system needs.

Start a conversation ↗