ArchitectureMay 8, 202612 min read

Two products, two backends: why Xcapade runs on Strapi and Hodol doesn't

A founder pinged me on LinkedIn last week. He's building a yoga retreat platform and wanted to know which backend stack he should pick. “You built Hodol and Xcapade,” he wrote. “What did you go with?” I started typing “Strapi” and then deleted it. Started typing “Express and Prisma” and deleted that too. The honest answer is: I used both, for two products, in the same year, and the reasons I picked one over the other are exactly the conversation we should be having.

This post is the long-form version of the reply I eventually sent him. Two products. Two backends. Two opposite calls by the same developer at the same desk. Why neither was a mistake.

First, what Xcapade actually is

Xcapade isn't one app. It's two Next.js applications sitting on top of a single Strapi backend:

  • The marketplace (xcapade.in) — where travellers discover and book rafting in Rishikesh, paragliding in Bir, treks in Himachal. Next.js 16, React 19, TypeScript, NextAuth + Firebase for consumer login, Leaflet for maps, Razorpay for checkout, Zustand for client state. Built for someone on a phone in a hotel lobby trying to book something for tomorrow morning.
  • The vendor SaaS (admin.xcapade.in) — where operators run their business. Listing management, reservations, spot bookings with conversation threads, settlement tracking, calendar, invoices, real-time notifications. Next.js 15, React 18, shadcn/ui, Socket.IO client, jose for JWT, TipTap for rich-text editing, jspdf for invoice PDFs. Built for someone behind a counter at 7am sorting tomorrow's departures.
  • Strapi — the brain. One backend, custom controllers and services for the business logic, RBAC for ADMIN vs VENDOR, REST API consumed by both frontends, Socket.IO for real-time on top. Hosted on the same Vultr VPS as everything else, PM2 + nginx in front, certbot for SSL. Two apps, one source of truth.

People hear “tourism platform” and they assume Shopify. I get it asked all the time. But Shopify is for selling products. A rafting trip is not a product. It has a date, a slot, a guide, a weather dependency, a safety waiver, a group size, a vendor commission cut, and a settlement timeline. There's nothing on Shopify that would do that without me hacking together five apps and a webhook duct-tape. The Shopify question was never the real question. The real question was: given that I'm building this from scratch, what backend doesn't make me hate myself in eighteen months?

Why Strapi turned out to be the right call

I want to be honest about how this decision actually got made. It wasn't a clean architecture session in front of a whiteboard. It was three days of paper sketches, two abandoned Express scaffolds, and a 1am moment where I realised I was about to write my own admin UI from scratch for the third time in my career and I genuinely could not face it.

Strapi solved five problems at once for Xcapade, and each of them would have cost me a week or more if I'd done it manually:

  • Content modelling that fits a marketplace. Xcapade has activities, destinations, categories, vendor profiles, SEO landing pages, blog content, FAQs, taxonomies. That's not data — that's content. Strapi's content-type builder lets me model all of it through a UI, ship a migration, and have the API live in five minutes. The same work in raw Prisma + Express would have taken a week of typing.
  • An admin UI I didn't have to build. Strapi's admin panel is where I (and eventually an ops person) onboard vendors, moderate listings, edit destination pages, configure the homepage. It's not pretty, but it's there on day one, free, with permissions and audit trails. Building this myself would have been a month of work that adds zero customer value.
  • RBAC out of the box. Xcapade has at least three role shapes — platform admin, vendor, end user. Strapi's users-permissions plugin handles this with route-level granularity. I extend it with custom policies for vendor-scoping (a vendor can only see their own reservations), but the foundation is already there.
  • One backend, two frontends. The marketplace and the vendor SaaS are wildly different products, but they read from the same data. A vendor edits a listing in the admin app; the marketplace sees the update on the next ISR revalidation. No sync code. No duplicated schemas. Strapi's REST endpoints serve both apps with the same controller; auth context differs, the data doesn't.
  • Custom services where it matters. The genuinely novel logic in Xcapade — spot bookings, commission calculations, settlement runs, vendor payouts, conversation threads — lives in custom Strapi services I wrote by hand. Strapi gets out of the way completely there. I'm not fighting a CMS; I'm using it as a host for my own code with a polished admin attached.

The parts of Strapi nobody warns you about

Strapi got me 60% of the backend for free. The other 40% included some genuinely annoying surprises I want to name out loud, because every blog post on the internet pretends Strapi is frictionless and it's not.

  • Version upgrades hurt. Going from Strapi v3 to v4 was infamous in the community. v4 to v5 wasn't fun either. Plugins drift, content types need migration, the admin panel gets reorganised. Once a year, I plan for a full weekend of upgrade work. I've never finished one in a single weekend.
  • Customising the admin UI is finicky. If you want the Strapi admin to look like your product, prepare for a slog. I gave up. The Strapi admin is for me and ops; vendors and travellers never see it. That clarity saved me months.
  • Auth needs help for consumer use. Strapi's users-permissions is fine for internal users. For the marketplace, where I want social login, magic links, phone OTP, and a slick onboarding, I layered NextAuth + Firebase in front and only synced the user record into Strapi after first login. Two auth systems sounds gross. It's actually fine — each does what it's good at.
  • Real-time is not a Strapi job. Strapi doesn't do websockets natively. For Xcapade's spot-booking chat and live notifications, Socket.IO lives alongside Strapi on the same Node process, hooking into the same auth context. It works, it's reliable, but you have to wire it yourself. No magic.
  • Performance ceilings exist. Strapi's default REST handlers are not the fastest thing in the world. For high-traffic listing pages, the marketplace caches aggressively (Next.js ISR, sometimes Redis in front of Strapi). I've never needed to rewrite a hot path yet, but I have a list of three endpoints I'd move to a leaner service if traffic 10x'd tomorrow.

So why didn't I use Strapi for Hodol?

This is the part of the conversation the LinkedIn founder kept circling back to. If Strapi is so good for Xcapade, why is Hodol on Express + Prisma + Supabase? Surely a wellness-business SaaS is even more “content-shaped” than a tourism marketplace?

The short answer: Hodol's product is the backend. The long answer is worth unpacking, because it's the actual decision principle.

  • Hodol is multi-tenant in a way Strapi fights. Every merchant on Hodol has their own storefront, their own custom domain, their own encrypted Razorpay keys, their own RLS policies on Postgres. The schema has tenantId on almost every row, with a Prisma extension that auto-scopes every query and middleware that verifies the user belongs to the resolved tenant. That kind of isolation is at the core of every read and write. Strapi's admin and content APIs are not built around this shape. I would have spent the first three months bending Strapi against its grain.
  • Hodol's “admin UI” is the product. The merchant dashboard isn't something I build because the product needs admin; it is the product. It has to be branded, fast, mobile-friendly, and shaped exactly the way a café owner expects. Strapi's admin panel would have been actively misleading there. I needed full control over every pixel.
  • Payment infrastructure is non-negotiable. Hodol holds per-merchant payment credentials, encrypted with AES-256-GCM, decrypted only at the point of use, never logged, never breadcrumbed. Webhook routing is per-tenant. Settlement reconciliation runs on BullMQ. This is the kind of code I want to write by hand, read in full, and own end to end. A CMS abstraction in the middle would be a liability.
  • Hodol has very little “content”. A merchant's products, bookings, orders, customers — that's transactional data, not editorial content. There's no taxonomy of destinations or curated SEO landing pages or marketing copy that ops needs to edit. The whole reason Strapi shines for Xcapade is the reason it's a poor fit for Hodol.

Put bluntly: Xcapade is a content-rich, two-sided platform where ops and vendors need to manage stuff every day. Hodol is a SaaS where the database schema, the tenant isolation model, and the payment plumbing are the value proposition. Different products. Different backbones. Same engineer, opposite calls, no contradiction.

The infrastructure both products share

Here's a fun footnote: Xcapade's Strapi backend, the marketplace, the vendor SaaS, and Hodol all live on a small fleet of Vultr VPSes — not Vercel, not AWS, not any serverless thing. Each app runs under PM2 in fork mode (cluster mode breaks Next.js standalone starts after about thirty restarts, ask me how I know), fronted by nginx, with Let's Encrypt for SSL. Predictable cost, predictable performance, no surprise invoice at the end of the month when someone scrapes my product pages too aggressively.

The whole stack — two Xcapade apps, one Strapi, one Hodol API, three frontends, a Redis instance, a Postgres — costs less per month than a single seat at most modern observability tools. As a solo developer with one customer base to answer to, that math matters every single time.

The actual principle, in one paragraph

If your product is mostly content, workflows, and an admin people other than you will use — Strapi (or Sanity, or Payload) pays you back in weeks, not months. If your product is transactional infrastructure, multi-tenant isolation, payment plumbing, or anything where the schema and the auth model are the differentiator — write your own backend. The mistake isn't picking the wrong tool. The mistake is picking any tool before you've stared at the actual shape of what you're building for long enough to know which one of those buckets it falls into.

I send the LinkedIn founder a much shorter version of this. He builds a retreat platform on Strapi. Six months in, he's shipping. Sometimes the cleanest engineering decision is not the one that's most opinionated. It's the one that's most honest about what you're actually building, and what you're absolutely not going to enjoy doing by hand at 2am on a Saturday.

Picking a backend for your next project and want a second opinion? Let's talk on WhatsApp, or read the Hodol case study. The full Xcapade case study is coming soon.