Skip to content
← Back to portfolio

Project case study · Web system

Wandity

My travel community platform, built with a Remix frontend and separate Express APIs for users, community, and guides. The stack includes React, Redis, Prisma, PostgreSQL, Stripe, AWS services, Docker, and Fly.io.

RemixReactTailwind CSSExpressNode.jsJWTRefresh TokensRedisPrismaPostgreSQLStripeSendGridTwilioAWS S3AWS RekognitionSharpPuppeteerDockerGitHub ActionsFly.io
Wandity

A travel community platform I built

Wandity is my own venture. I built it end to end as a social network for travellers, with a Remix and React frontend backed by separate Express services for users, community, and guides.

Interested in a preview? Get in touch.

Architecture

The Users API handles authentication, credits, orders, and Stripe checkout. The Community API handles feed items, comments, votes, subscriptions, and reports. A separate Guides API handles guides, places, itineraries, and guide media. The Remix frontend calls these APIs and uses the generated Prisma clients published for the services.

Each backend uses Express, Node.js, Prisma, and PostgreSQL through its own database URL and Prisma client. Redis provides refresh-token blacklisting in the Users service and link-preview caching in the Community service.

Credit-gated public feed

A post is not immediately public: community feed queries require pushed items. The Remix flow checks that an item is not already pushed, rejects an explicitly unapproved item, verifies the user has enough credits, and submits the item to the Community API with a normal or premium queue choice. After the enqueue succeeds, it decreases the credit balance and records a negative Wandity transaction.

The Community schema stores four queue lists: normal, premium, normal-flagged, and premium-flagged. The UI shows normal placement at one credit. Premium placement costs three credits per current premium-queue item, with a three-credit minimum. A ten-minute refresher promotes the first premium item when one is available, or the first normal item otherwise. It marks the item as pushed with a timestamp and removes it from the active queue.

The moderation path includes the flagged queues, but the current enqueue helper hard-codes isFlagged to false. The refresher processes only the normal and premium queues.

Community access

Comments are represented by the same Item model as other community content; COMMENT is one of its item types. The code applies a general subscription check when a non-owner creates an item in another user’s feed and for several item operations, rather than defining a distinct comments-only subscriber gate in the schema.

Authentication and payments

  • JWT access and refresh tokens.
  • Redis-backed refresh-token blacklist.
  • Magic-link email delivery through SendGrid.
  • SMS verification through Twilio.
  • Stripe Checkout and webhook handling for credit purchases.
  • Image uploads are resized with Sharp and stored in AWS S3.
  • Amazon Rekognition runs face detection and validation against S3 images.
  • Link previews use metadata fetching with a Puppeteer fallback and Redis caching.

Database and deployment

Each backend has a single PostgreSQL datasource and Prisma client. The clients include region-aware support for read-replica routing: when the Fly region differs from PRIMARY_REGION, the code switches the database connection to port 5433. The checked-in Fly configurations are single-region, with every service using fra; the deployment documentation describes adding more regions as a follow-up.

The services have Dockerfiles and GitHub Actions workflows for Fly.io deployment. The configuration described here is the current single-region setup, not a deployed multi-region replica topology.

Have a project in mind?

Tell me what you're working on and where you need help.