Foodtopia
A trust-weighted restaurant discovery app where credibility shapes what you see.
The Challenge
The core challenge was designing a trust system that is both mathematically meaningful and resistant to gaming. The credibility score (0-100) is a weighted composite of account age, review volume, upvote ratio, follower count, recent activity, and verified status. Critically, votes themselves are weighted by the voter credibility — a verified critic upvote contributes 2.5x more than a new account. This creates a self-reinforcing quality loop without requiring manual moderation. On the mobile side, building a real-time social feed across iOS and Android with optimistic UI updates, location-aware discovery, and multi-photo uploads via Cloudinary — while maintaining a smooth glass-morphism aesthetic with blur layers and gradients — required careful performance tradeoffs.
Architectural Decisions
Credibility-weighted voting engine
Instead of treating all votes equally, each vote carries a multiplier (0.5x-2.5x) based on the voter credibility score. This was implemented in a dedicated credibility.ts service that recomputes scores on vote events. The alternative — equal-weight votes — was rejected because it makes the platform trivially gameable.
Fastify with Prisma over REST convention
Fastify was chosen over Express for its schema-based performance characteristics and native TypeScript support. Combined with Prisma type-safe query builder, every API response is validated end-to-end: Zod validates incoming payloads, Prisma enforces schema correctness, and TypeScript catches type mismatches at compile time.
Cloudinary for image uploads at the edge
Rather than routing photo uploads through the backend, photos are uploaded directly from the mobile client to Cloudinary using a public upload preset. The backend only stores the returned URL. This offloaded resizing, CDN distribution, and format conversion to a specialized service.
React Context API over Redux or Zustand
Global state is deliberately minimal — only auth tokens and saved restaurant IDs need cross-screen access. Using Context API with hooks avoided the boilerplate overhead of Redux for a scope that did not warrant it. SavedContext uses optimistic updates to make saves feel instant.
Prisma migrations as living schema documentation
Nine incremental migrations capture the product evolution — from basic auth, through restaurant reviews and votes, to a full social graph with follows, comments, lists, and achievements. Each migration is a readable artifact of product decisions.
Achievement system with credibility thresholds
Achievements are evaluated in a dedicated achievements.ts service against the user current stats. Rather than triggering achievements via database triggers, they are computed server-side on relevant events. This keeps the logic testable, readable, and easy to extend.