A full stack sustainability platform that reduces household food waste through AI powered inventory tracking, a peer to peer marketplace, and a gamified reward system.
EcoPlate transforms how households manage food consumption by combining OpenAI Vision receipt scanning, ML price recommendations, a marketplace for users to list near expiry food items for sale or free pickup with real-time WebSocket messaging, and a CO2 gamification engine, turning sustainable actions into points, badges, and leaderboard achievements.
Built with React 19, TypeScript, Bun, SQLite (Drizzle ORM), Capacitor (Android/iOS), and Tailwind CSS + shadcn/ui.
Originally deployed on AWS EC2 as per screenshots, then migrated to Railway to reduce hosting cost and simplify deployment operations.
| Messaging (WebSockets) | Consumption Tracking |
|---|---|
![]() |
![]() |
| Dashboard | Marketplace |
|---|---|
![]() |
![]() |
| EcoPoints & Rewards |
|---|
![]() |
- Track food items with expiration dates and CO2 emission data
- Receipt scanning via OpenAI Vision: snap a photo, items are parsed and added automatically
- Log consumption, waste, sharing, and sales with sustainability metrics per action
- List near expiry food items for sale or free pickup
- Browse listings with geolocation based map view
- ML powered price recommendations for optimal listing pricing
- Real time in app messaging between buyers and sellers via WebSockets
- Complete transactions to earn EcoPoints tied to CO2 savings
- Earn points calculated from CO2 savings (CO2 value x 1.5, minimum 3 points per action)
- 16 unlockable badges across 4 categories: Milestones, Waste Reduction, Sharing, and Streaks
- Daily streak tracking with milestone notifications (3, 7, 14, 30 days and beyond)
- Community leaderboard ranked by lifetime points
- Redeem points for rewards
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Database | SQLite via bun:sqlite |
| ORM | Drizzle ORM |
| Backend | Bun native HTTP server + WebSockets |
| Frontend | React 19 + TypeScript |
| Build Tool | Vite |
| Styling | Tailwind CSS + shadcn/ui |
| Mobile | Capacitor (Android & iOS) |
| AI/ML | OpenAI Vision API, Python recommendation engine |
| Auth | JWT (jose library) |
| Validation | Zod |
flowchart TD
START((Start)) --> AUTH{Authenticated?}
AUTH -- No --> LOGIN[Login/Register]
LOGIN --> DASHBOARD
AUTH -- Yes --> DASHBOARD[Dashboard]
DASHBOARD --> FRIDGE[MyFridge]
DASHBOARD --> MARKET[Marketplace]
DASHBOARD --> ECO[EcoPoints]
subgraph MyFridge["MyFridge Module"]
FRIDGE --> ADD[Add Product]
FRIDGE --> SCAN[Scan Receipt]
FRIDGE --> VIEW[Track Consumption and Waste]
SCAN -- AI Processing --> PARSE[Parse Items]
PARSE --> ADD
VIEW --> CONSUME{Action?}
CONSUME -- Consumed --> POINTS_CONSUMED["+5 Points"]
CONSUME -- Shared --> POINTS_SHARED["+10 Points"]
CONSUME -- Sold --> POINTS_SOLD["+8 Points"]
CONSUME -- Wasted --> POINTS_WASTED["-3 Points"]
end
subgraph Marketplace["Marketplace Module"]
MARKET --> BROWSE[Browse Listings]
MARKET --> CREATE[Create Listing]
MARKET --> EDIT[Edit Listing]
MARKET --> DELETE_L[Delete Listing]
MARKET --> MapView[Geolocation]
BROWSE --> MESSAGE[Message Seller]
CREATE --> PRODUCT[Get Product Recommendation]
MARKET --> COMPLETE[Complete Listing - Sold]
COMPLETE --> POINTS_SOLD
end
subgraph Gamification["Gamification Module"]
ECO --> STATS[View Stats]
ECO --> BADGES[View Badges]
ECO --> LEADER[Leaderboard]
ECO --> REWARDS[Redeem Rewards]
STATS --> UNLOCK{Badge Unlock?}
UNLOCK -- Yes --> BADGES
end
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Register new user |
| POST | /api/v1/auth/login |
Login |
| POST | /api/v1/auth/refresh |
Refresh access token |
| POST | /api/v1/auth/logout |
Logout |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/myfridge/products |
List all products |
| POST | /api/v1/myfridge/products |
Add product |
| GET | /api/v1/myfridge/products/:id |
Get product |
| PATCH | /api/v1/myfridge/products/:id |
Update product |
| DELETE | /api/v1/myfridge/products/:id |
Delete product |
| POST | /api/v1/myfridge/products/:id/consume |
Log consumption |
| POST | /api/v1/myfridge/receipt/scan |
Scan receipt (AI) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/marketplace/listings |
Browse active listings |
| GET | /api/v1/marketplace/listings/nearby |
Get nearby listings (geolocation) |
| GET | /api/v1/marketplace/my-listings |
Get user's own listings |
| GET | /api/v1/marketplace/listings/:id |
Get listing details |
| POST | /api/v1/marketplace/listings |
Create new listing |
| PATCH | /api/v1/marketplace/listings/:id |
Update listing |
| DELETE | /api/v1/marketplace/listings/:id |
Delete listing |
| POST | /api/v1/marketplace/listings/:id/complete |
Mark as sold/completed |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/gamification/dashboard |
Dashboard summary |
| GET | /api/v1/gamification/points |
Points breakdown & history |
| GET | /api/v1/gamification/badges |
All badges with progress |
| GET | /api/v1/gamification/metrics |
Sustainability metrics |
| GET | /api/v1/gamification/leaderboard |
Community leaderboard |
sequenceDiagram
participant C as Client
participant A as API Server
participant DB as Database
Note over C,DB: Registration
C->>A: POST /auth/register
A->>DB: Create user + hash password
DB-->>A: User created
A->>A: Generate JWT tokens
A-->>C: { accessToken, refreshToken }
Note over C,DB: Login
C->>A: POST /auth/login
A->>DB: Verify credentials
DB-->>A: User data
A->>A: Generate JWT tokens
A-->>C: { accessToken, refreshToken }
Note over C,DB: Protected Request
C->>A: GET /myfridge/products<br/>Authorization: Bearer {token}
A->>A: Verify JWT
A->>DB: Query products
DB-->>A: Products list
A-->>C: [products]
Note over C,DB: Token Refresh
C->>A: POST /auth/refresh
A->>DB: Verify refresh token
A->>A: Generate new tokens
A-->>C: { accessToken, refreshToken }
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend
participant AI as OpenAI Vision
U->>F: Upload receipt image
F->>F: Convert to Base64
F->>B: POST /myfridge/receipt/scan
B->>AI: Analyze image
AI-->>B: Extracted items JSON
B-->>F: { items: [...] }
F->>U: Show parsed items
U->>F: Confirm items
F->>B: POST /myfridge/products (batch)
B-->>F: Products created
F->>U: Success + points awarded
ecoplate/
├── backend/
│ ├── src/
│ │ ├── index.ts # Server entry point
│ │ ├── routes/ # API route handlers
│ │ │ ├── auth.ts # Authentication
│ │ │ ├── myfridge.ts # Fridge management
│ │ │ ├── marketplace.ts # Marketplace
│ │ │ └── gamification.ts # EcoPoints & Badges
│ │ ├── services/ # Business logic
│ │ │ ├── gamification-service.ts
│ │ │ └── badge-service.ts
│ │ ├── middleware/ # Auth middleware
│ │ ├── db/
│ │ │ ├── schema.ts # Drizzle ORM schema
│ │ │ ├── migrations/ # Database migrations
│ │ │ └── seed.ts # Seed data
│ │ └── utils/
│ └── public/ # Built frontend assets
├── frontend/
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── pages/ # Page components
│ │ ├── contexts/ # React contexts
│ │ ├── services/ # API client + Capacitor utils
│ │ ├── hooks/ # Custom hooks
│ │ └── lib/ # Utilities
│ ├── android/ # Android native project
│ └── capacitor.config.ts # Capacitor config
├── recommendation-engine/
│ ├── app.py # ML recommendation service
│ ├── Dockerfile # Container deployment
│ └── requirements.txt # Python dependencies
└── deploy/ # Legacy AWS/EC2 deployment files
- Bun v1.0+
- (Optional) Android Studio with SDK 33+ for mobile builds
# Install dependencies
bun install
# Set up environment
cp .env.example .env
# Run migrations and seed
bun run db:migrate && bun run db:seed
# Start development servers
bun run dev:backend # Terminal 1 → http://localhost:3000
bun run dev:frontend # Terminal 2 → http://localhost:5173Or use the convenience scripts:
# Mac/Linux
./scripts/start-all.sh
# Windows (PowerShell)
.\scripts\start-all.ps1# Mac/Linux
./scripts/build.sh
# Windows
.\scripts\build.ps1
# Serve
cd backend && bun run src/index.ts # → http://localhost:3000EcoPlate is currently deployed on Railway using the root Dockerfile, which builds the Vite frontend and serves the compiled assets from the Bun backend in a single container.
Railway provides the runtime PORT automatically. Production secrets such as JWT_SECRET, OPENAI_API_KEY, and VITE_GOOGLE_MAPS_API_KEY should be configured in the Railway service variables.
The previous AWS EC2 deployment assets are kept under deploy/ for reference, but Railway is the current hosting target because it is simpler to operate and more cost efficient for this project.
./scripts/build-android.sh # or .\scripts\build-android.ps1
cd frontend && bunx cap open androidAPK output: frontend/android/app/build/outputs/apk/debug/app-debug.apk
Note: Capacitor 6.x + Android Gradle Plugin 9.0+ compatibility patches are applied automatically via
bun install. Seedocs/android-build-patching.mdfor details.
| Variable | Description | Required |
|---|---|---|
PORT |
Server port (default: 3000) | No |
JWT_SECRET |
Secret key for JWT signing | Yes (production) |
OPENAI_API_KEY |
OpenAI API key for receipt scanning | No |
SQLite database stored at backend/ecoplate.db:
| Domain | Tables |
|---|---|
| Users | users |
| MyFridge | products, product_interaction |
| Marketplace | marketplace_listings, image_listing, conversation, message |
| Gamification | user_points, badges, user_badges, product_sustainability_metrics |
MIT






