Reduce food waste. Save money. Track what matters.
FreshTrack is a pantry management dashboard that helps you track food freshness, get alerts before items expire, discover recipes to use expiring ingredients, and visualize waste patterns over time.
Live app: https://freshtrack.up.railway.app
About 30-40% of food purchased by US households is wasted, costing the average family roughly $1,500/year. The root cause: people forget what's in their pantry. Items expire unnoticed, meals are not planned around what needs using first, and there is no feedback loop showing how much waste actually occurs.
This habit does not just drain your wallet. It actively hurts the planet. Food waste drives climate change, causing roughly 8% of global greenhouse emissions. When we throw away food, we also waste the water, land, and energy used to grow it. That organic waste then rots in landfills and releases methane, a greenhouse gas far more potent than carbon dioxide. Because families lack a feedback loop showing how much waste actually occurs, this costly cycle continues completely unnoticed.
- Freshness Dashboard - Overview of active pantry items, urgency metrics, and the next recipe to try
- Expiration Alerts - Prominent warnings for items expiring within 2 days
- Pantry Management - Add, search, filter, sort, and track inventory by quantity, unit, purchase date, expiration date, and estimated cost
- Barcode Scanning - Scan a product barcode to prefill name, category, and expiration via Open Food Facts; works cross-browser (native Barcode Detection API with a ZXing fallback) and always offers manual entry
- Recipe Suggestions - "Use It Up" recipes that match ingredients expiring within 5 days
- Recipe Dive - Search and filter a catalog of recipes (imported from TheMealDB), ranked by how many of your expiring ingredients each one uses
- Consume/Waste Logging - Mark items as used or wasted, log outcomes, and undo recent actions
- Statistics & Charts - Monthly trends, waste rates, category breakdowns, and money saved estimates
- Google Sign-In - Auth.js-powered Google OAuth gate for the application
- Google is the only configured sign-in provider.
- Production runs on Railway with Railway Postgres.
- Local development uses PostgreSQL through
DATABASE_URL. - Pantry, recipe, and waste rows are scoped per authenticated user.
- Built-in categories and starter recipes are global seed data.
- PWA assets, a web app manifest,
/privacy, and/termsare present.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 App Router |
| Language | TypeScript |
| Runtime | React 19 |
| Authentication | Auth.js / NextAuth v5 beta with Google OAuth |
| Database | PostgreSQL on Railway via postgres and Drizzle ORM |
| ORM | Drizzle ORM |
| Styling | Tailwind CSS v4 |
| UI Components | Radix UI primitives |
| Charts | Recharts |
| Motion | Framer Motion |
| Icons | Lucide React |
- Node.js 20.9+ and npm
- PostgreSQL, either local or Railway-hosted
- Google OAuth credentials for local sign-in
# Clone the repository
git clone <repo-url>
cd freshtrack
# Install dependencies
npm install
# Create local environment config
cp .env.example .env.localFill in .env.local:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/freshtrack
AUTH_SECRET=<generate with: openssl rand -base64 32>
AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=<google-oauth-client-id>
GOOGLE_CLIENT_SECRET=<google-oauth-client-secret>For Google OAuth local development, add these callback settings to a Google OAuth web client:
Authorized JavaScript origin: http://localhost:3000
Authorized redirect URI: http://localhost:3000/api/auth/callback/google
Then prepare the local database and start the app:
# Run database migrations
npm run db:migrate
# Seed the database with sample categories, items, recipes, and waste logs
npm run db:seed
# Start the development server
npm run devThe app will be available at http://localhost:3000 and will redirect unauthenticated users to /login.
| Command | Description |
|---|---|
npm run dev |
Start dev server with Turbopack |
npm run build |
Production build |
npm run start |
Start production server |
npm run lint |
Type-check the project |
npm run typecheck |
Type-check the project |
npm run test |
Run Vitest tests |
npm run icons:generate |
Regenerate PWA icon PNGs |
npm run db:seed |
Seed local dev database with sample data |
npm run db:seed:categories |
Seed production-safe global categories only |
npm run db:seed:recipes |
Seed production-safe global starter recipes only |
npm run db:import:recipes |
Import the global recipe catalog from TheMealDB (idempotent, production-safe) |
npm run db:generate |
Generate new migrations from schema |
npm run db:migrate |
Run pending migrations |
FreshTrack deploys as an installable PWA on Railway with Railway Postgres as the managed database.
Production URL: https://freshtrack.up.railway.app
See .env.example for the full template. Required in production:
DATABASE_URL- Railway Postgres connection stringAUTH_SECRET- generate withopenssl rand -base64 32AUTH_URL- deployed origin, for examplehttps://freshtrack.up.railway.appGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET- Google Cloud OAuth credentials
- Create a Railway project linked to this repo.
- Add a Railway Postgres service.
- Add the production environment variables in the Railway app service.
- Run migrations against production:
DATABASE_URL=... npm run db:migrate - Seed global categories:
DATABASE_URL=... npm run db:seed:categories - Seed global starter recipes:
DATABASE_URL=... npm run db:seed:recipes - Import the recipe catalog:
DATABASE_URL=... npm run db:import:recipes - Deploy the app service from Railway.
- Configure Google OAuth with:
- Authorized origin: your Railway public app URL
- Redirect URI:
<your Railway public app URL>/api/auth/callback/google - Privacy policy:
<your Railway public app URL>/privacy - Terms:
<your Railway public app URL>/terms
Do not run npm run db:seed against production. That command creates a local dev user and demo pantry data. It is guarded and only runs outside production with:
ALLOW_DESTRUCTIVE_SEED=1 npm run db:seed- Google sign-in
- Per-user pantry, recipe, and waste tracking
- Barcode scanning for quick item entry
- Global built-in categories and an imported recipe catalog with search, filters, and expiring-ingredient ranking
- Installable PWA manifest and icons
- Public privacy policy and terms pages
- Railway deployment with Railway Postgres
- Email and password auth
- Push notifications for expiring items
- Custom domain
- Household or shared pantry support
- Offline mode with a service worker
- Per-user custom categories
src/
├── app/ # Next.js App Router pages & API routes
│ ├── api/ # REST API endpoints and Auth.js route handler
│ ├── login/ # Google sign-in page
│ ├── pantry/ # Pantry management page
│ ├── recipes/ # Recipe suggestions page
│ ├── stats/ # Statistics & charts page
│ └── page.tsx # Dashboard
├── components/ # React components
│ ├── ui/ # Base UI components
│ ├── dashboard/ # Dashboard-specific components
│ ├── pantry/ # Pantry page components
│ ├── recipes/ # Recipe components
│ ├── stats/ # Chart components
│ └── layout/ # App shell, navigation, FAB, and undo toast
├── db/ # Database layer
│ ├── schema.ts # Drizzle ORM schema
│ ├── index.ts # Postgres database connection
│ ├── seed.ts # Local dev seed script with realistic data
│ ├── seed-categories.ts # Production-safe global category seed
│ └── seed-recipes.ts # Production-safe global recipe seed
├── lib/ # Shared utilities
└── types/ # NextAuth type augmentation
The active Drizzle schema includes pantry tables (categories, items), recipe tables (recipes, recipe_ingredients), waste tracking (waste_log), and Auth.js tables (users, accounts, sessions, verification_tokens).
Local development uses the DATABASE_URL in .env.local, usually a local Postgres database or a Railway-provided development connection string. Production uses Railway Postgres through the same Drizzle Postgres driver.
Most application routes are protected by middleware and require an authenticated session. Auth routes and the login route are public.
| Method | Endpoint | Description |
|---|---|---|
| GET/POST | /api/auth/[...nextauth] |
Auth.js route handler |
| GET | /api/items |
List active pantry items |
| GET | /api/items?status=consumed |
List items by status: active, consumed, or wasted |
| POST | /api/items |
Add a new active item |
| PATCH | /api/items/:id |
Update an item |
| DELETE | /api/items/:id |
Remove an item |
| POST | /api/items/:id/consume |
Mark an active item as consumed and log it |
| POST | /api/items/:id/waste |
Mark an active item as wasted and log it |
| POST | /api/items/:id/restore |
Restore a consumed/wasted item to active and remove the latest matching log |
| GET | /api/categories |
List food categories |
| GET | /api/products/:barcode |
Look up a product by barcode (Open Food Facts, with a UPCitemdb name fallback) for add-item prefill |
| GET | /api/recipes |
List recipes with ingredients; supports search, cuisine, category, maxMinutes, and sort, and annotates each with expiring-ingredient matches |
| GET | /api/recipes/suggestions |
Recipes using active items expiring within 5 days |
| GET | /api/stats |
Waste and consumption statistics |