That 4.2-star restaurant? Most of those reviews are from 2022. The chef changed. The oil changed. The rating didn't.
FreshBite is a production-ready web application that revolutionizes how people review dishes at restaurants by showing only time-windowed, fresh reviews. Built as a complete MVP β because a review from a week ago is data, a review from a year ago is noise.
Built with Next.js 14 + Spring Boot + FastAPI + PostgreSQL, deployed on Vercel + DigitalOcean Droplet with Docker. Features time-windowed review queries, live risk scoring, GPS-based restaurant discovery, and LLM-powered insights.
Build a web app where users can review specific dishes at specific restaurants, but the UI displays ONLY the most recent 5 days of reviews by default.
Why? Dish quality changes daily based on:
- Chef shifts and skill levels
- Fresh vs. reused ingredients (oil, spices)
- Time of day and meal preparation
- Kitchen management changes
Solution: Store all historical reviews, but surface only fresh data in the UI.
- β Dish-specific reviews at restaurant locations (DishAtRestaurant entity)
- β Time-windowed queries (24h, 48h, 5d)
- β Risk labels (π’ Good, π‘ Mixed, π΄ Risky, βͺ No data)
- β Real-time stats (avg rating, review count)
- β Add reviews with server-generated UTC timestamps
- β Responsive UI with Tailwind CSS
- β SSR-first Next.js App Router architecture
- β Production-ready PostgreSQL schema with proper indexes
- β Per-page visit counters (unique + total views)
- β Global page tracker (Next.js client + API)
- β API endpoints for analytics
- Evidence-based chat (RAG over time-windowed reviews with citations)
- Alert subscriptions (notify when quality drops)
- Meal slot awareness (breakfast/lunch/dinner breakdowns)
- Time-of-day filtering (show only lunch reviews)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (Vercel) β
β Next.js 14 (App Router) + TypeScript + Tailwind CSS β
β β
β Pages: β
β β’ / β Homepage (browse dishes) β
β β’ /dish/[id] β Dish page (reviews + stats + form) β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Backend (Next.js API) β
β β
β Routes: β
β β’ GET /api/dish/[id]/reviews?window=5d β
β β’ POST /api/dish/[id]/reviews β
β β’ GET /api/dish/[id]/summary?window=24h β
β β’ POST /api/chat (stub) β
β β’ POST /api/alerts/run (stub) β
β β’ POST /api/analytics/pageviews β Page visit tracking (POST/GET)
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Database (Neon PostgreSQL) β
β Prisma ORM β
β β
β Tables: β
β β’ Restaurant β
β β’ Dish β
β β’ DishAtRestaurant (first-class entity) β
β β’ Review (append-only, never deleted) β
β β’ AlertSubscription (stub) β
β β’ PageVisit β
β β’ PageVisitCounter β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DishAtRestaurant (Why it matters)
- "Chicken Biryani" at Restaurant A β same dish at Restaurant B
- Enables per-location freshness tracking
- First-class entity for reviews and alerts
Review (Append-only log)
- Never deleted (freshness enforced by queries, not deletes)
createdAtin UTCmealSlotnullable (planned feature)
-- Optimized for time-window queries
CREATE INDEX idx_review_dish_time ON Review(dishAtRestaurantId, createdAt DESC);- Node.js 18+
- PostgreSQL database (or Neon free tier)
- npm or yarn
git clone <your-repo-url>
cd freshbite
npm install# Create .env file
cp .env.example .env
# Edit .env and add your DATABASE_URL:
# DATABASE_URL="postgresql://user:password@host:5432/freshbite?sslmode=require"# Generate Prisma client
npm run db:generate
# Push schema to database
npm run db:push
# Seed demo data
npm run db:seednpm run devAfter seeding, verify the following:
- Homepage shows the seeded dish
- Dish page displays 7 recent reviews (within last 5 days)
- Old reviews (>5 days) are hidden from the feed
- Stats reflect only the recent reviews
- Risk badge shows correct status based on 24h window
- Changes based on recent review quality
- "Not enough data" when < 3 reviews
- Can post new review with 1-5 rating
- Review appears immediately in feed
- Stats update correctly
- Server-generated timestamp (check DB)
# Get reviews (5 day window)
curl http://localhost:3000/api/dish/[ID]/reviews?window=5d
# Get summary (24h window)
curl http://localhost:3000/api/dish/[ID]/summary?window=24h
# Post review
curl -X POST http://localhost:3000/api/dish/[ID]/reviews \
-H "Content-Type: application/json" \
-d '{"rating": 5, "text": "Amazing!"}'
# Chat stub
curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"dishAtRestaurantId": "[ID]", "question": "How is it?", "window": "24h"}'- Go to neon.tech
- Create free PostgreSQL database
- Copy connection string
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
# Set environment variables in Vercel dashboard:
# DATABASE_URL=<your-neon-connection-string>
# NEXT_PUBLIC_BASE_URL=https://your-app.vercel.app# Generate Prisma client in production
vercel env pull .env.production
npm run db:push
npm run db:seedRequired Secrets:
SITE_URL- Your deployed app URLALERTS_SECRET_TOKEN- Random secure token
Add in GitHub repo β Settings β Secrets
freshbite/
βββ app/
β βββ api/ # API routes
β β βββ dish/[id]/
β β β βββ reviews/
β β β β βββ route.ts # GET/POST reviews
β β β βββ summary/
β β β βββ route.ts # GET summary + risk
β β βββ chat/
β β β βββ route.ts # POST chat (stub)
β β βββ alerts/
β β βββ run/
β β βββ route.ts # POST alerts (stub)
β βββ dish/[id]/
β β βββ page.tsx # Dish detail page
β βββ layout.tsx
β βββ page.tsx # Homepage
β βββ globals.css
β βββ not-found.tsx
βββ components/
β βββ ChatPanel.tsx
β βββ DishHeader.tsx
β βββ ReviewCard.tsx
β βββ ReviewFeed.tsx
β βββ ReviewForm.tsx
β βββ RiskBadge.tsx
β βββ StatsPanel.tsx
βββ lib/
β βββ format-time.ts # Relative time formatting
β βββ prisma.ts # Prisma client singleton
β βββ risk-label.ts # Risk calculation logic
β βββ time-window.ts # Window parsing (24h/48h/5d)
βββ prisma/
β βββ schema.prisma # Database schema
β βββ seed.ts # Seed script
βββ .github/
β βββ workflows/
β βββ ci.yml # CI: lint + typecheck + build
β βββ alerts-cron.yml # Hourly alerts (stub)
βββ package.json
βββ tsconfig.json
βββ tailwind.config.ts
βββ next.config.js
βββ .env.example
βββ README.md
# Development
npm run dev # Start dev server
npm run build # Build for production
npm run start # Start production server
npm run lint # Run ESLint
npm run typecheck # TypeScript type check
# Database
npm run db:generate # Generate Prisma Client
npm run db:push # Push schema to DB (no migrations)
npm run db:migrate # Create migration
npm run db:seed # Seed demo data
npm run db:studio # Open Prisma Studio- Problem: Old reviews mislead (chef changed, oil reused, ingredients differ)
- Solution: Show only recent data (5d default)
- Impact: Users see what's actually relevant today
- Problem: Same dish name at different locations = different quality
- Solution: First-class entity linking dish + restaurant
- Impact: Accurate per-location tracking
- Problem: Deleting old reviews loses historical context
- Solution: Store everything, filter at query time
- Impact: Can analyze trends, add ML later
- Problem: Users don't have time to read all reviews
- Solution: Color-coded summary (Good/Mixed/Risky)
- Impact: Instant decision-making
- RAG over time-windowed reviews
- Answer questions with citations
- Example: "Is it spicy?" β Shows relevant review quotes
- Subscribe to dishes
- Email/SMS when quality drops
- Configurable thresholds
- Derive breakfast/lunch/dinner from timestamp + timezone
- Filter reviews by time of day
- "Show me only lunch reviews"
- Quality trends over time
- Chef/shift correlation
- Predictive risk scoring
Contributions welcome! Key areas:
- UI/UX improvements (mobile responsiveness, accessibility)
- Performance optimizations (caching, edge functions)
- Feature implementations (chat, alerts, meal slots)
- Testing (unit tests, E2E tests)
MIT License - See LICENSE file
Built with:
Questions or issues?
- Open a GitHub issue
- Check the Testing Checklist
- Review Deployment Docs
FreshBite is a Next.js 14 + Prisma + Neon PostgreSQL app for dish reviews, with time-windowed freshness logic and per-page analytics. Key features:
- Dish-specific reviews at restaurant locations
- Time-windowed queries (last 5 days)
- Risk labels (Good/Mixed/Risky)
- Real-time stats
- Per-page visit counters (unique + total views)
- Append-only review log
- Responsive UI (Tailwind)
- SSR-first architecture
- Production-ready DB schema
- API endpoints for reviews, summaries, chat (stub), alerts (stub), analytics
Data Model:
- Restaurant
- Dish
- DishAtRestaurant (per-location dish entity)
- Review (append-only)
- AlertSubscription (stub)
- PageVisit (event log)
- PageVisitCounter (aggregated counters)
Analytics:
- Page views tracked via client-side tracker and API
- Unique visitors via session cookie
- Bot traffic ignored
Deployment:
- Vercel (frontend + API)
- Neon PostgreSQL
- Prisma ORM
How to extend:
- Add new API endpoints in
app/api/ - Add new DB models in
prisma/schema.prisma - Add new client components in
components/ - Analytics API is ready for dashboard or admin usage
Contact:
- Open GitHub issue for questions
Built with β€οΈ for better food decisions