Full-stack nutrition tracking app for logging meals, managing macro targets, and adding foods manually or from a barcode-backed product lookup.
| Area | Details |
|---|---|
| Frontend | React 19, TypeScript, Vite, Tailwind CSS 4 |
| Backend | Express 5, MySQL 8, JWT auth |
| Input methods | Manual ingredients, saved meals, barcode product lookup |
| User features | Macro targets, daily log, history snapshots, optional Google OAuth |
| Delivery | SPA frontend with PWA support |
- Daily calorie, protein, carb, and fat tracking
- Custom ingredients and reusable meals
- Logged meals for the current day with history snapshots
- Barcode-based product lookup with Scanbot Web SDK
- JWT authentication with optional Google OAuth login
- Internationalization via i18next
- PWA-enabled frontend build via Vite
| Frontend | Backend |
|---|---|
| React 19 | Express 5 |
| TypeScript 5 | MySQL 8 via mysql2 |
| Vite 7 | JWT + bcrypt |
| Tailwind CSS 4 | Passport Google OAuth 2.0 |
| react-i18next | Helmet, CORS, express-rate-limit |
| react-select | express-validator |
| react-circular-progressbar | |
| Scanbot Web SDK | |
| vite-plugin-pwa |
- Node.js 20.19+ or 22.12+
- npm
- MySQL 8+
- Optional: Google OAuth credentials
git clone <repo-url>
cd nutrition-app
cd client
npm install
cd ../server
npm install
cd ..
mysql -u root -p < database/schema.sqlCreate your environment files, start the backend and frontend, then open http://localhost:5173.
git clone <repo-url>
cd nutrition-appcd client
npm install
cd ../server
npm installmysql -u root -p < database/schema.sqlmacOS/Linux:
cp server/.env.example server/.env
cp client/.env.example client/.envPowerShell:
Copy-Item server/.env.example server/.env
Copy-Item client/.env.example client/.envServer variables in server/.env:
DBHOSTDBPORTDBUSERDBPASSWORDDBNAMEJWT_SECRETSESSION_SECRETFRONTEND_URLPORTGOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETif Google login is enabled
Client variables in client/.env:
VITE_API_URL, for examplehttp://localhost:3001
Backend:
cd server
npm startFrontend:
cd client
npm run devhttp://localhost:5173
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server |
npm run build |
Run TypeScript build and create a production bundle |
npm run preview |
Preview the production build locally |
npm run lint |
Run ESLint on src/ |
npm run format |
Format frontend source files with Prettier |
| Command | Description |
|---|---|
npm start |
Start the Express server |
npm run lint |
Run ESLint with auto-fix |
npm run format |
Format the server project with Prettier |
npm test |
Placeholder script, currently not implemented |
Frontend production build:
cd client
npm run buildOutput is generated in client/dist.
- The backend listens on
PORT, defaulting to3001. - The frontend expects the backend base URL from
VITE_API_URL. - CORS is restricted to
FRONTEND_URL. - Google OAuth is optional; leaving those credentials unset disables SSO in practice.
| Endpoint | Response |
|---|---|
GET /health |
{ "status": "ok" } |
All /api/* routes require Authorization: Bearer <token>.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/auth/register |
No | Register a new user with username, email, and password |
POST |
/auth/login |
No | Log in with username and password |
POST |
/auth/verifyToken |
Yes | Validate a token and return the authenticated user |
POST |
/auth/userInDb |
Yes | Check whether the token owner still exists |
GET |
/auth/google |
No | Start the Google OAuth flow |
GET |
/auth/google/callback |
No | OAuth callback that redirects back to the frontend with a token |
GET |
/auth/logout |
No | Log out the Passport session and redirect to the frontend |
| Method | Path | Description |
|---|---|---|
GET |
/api/data |
Fetch profile data and macro targets |
PUT |
/api/data |
Update profile data and macro targets |
GET |
/api/food |
Fetch ingredients, meals, and eaten items |
POST |
/api/ingredient |
Create an ingredient |
PUT |
/api/ingredient |
Update an ingredient |
DELETE |
/api/ingredient |
Delete an ingredient |
POST |
/api/meal |
Create a meal |
PUT |
/api/meal |
Update a meal |
DELETE |
/api/meal |
Delete a meal |
POST |
/api/eaten |
Log a meal as eaten |
DELETE |
/api/eaten |
Delete a single eaten entry |
DELETE |
/api/eaten/all |
Clear today's eaten entries |
POST |
/api/history |
Save a daily history snapshot |
GET |
/api/history |
Fetch history snapshots |
POST |
/api/product/search |
Search the shared product database by name |
POST |
/api/product/barcode |
Look up a product by barcode |
The schema lives in database/schema.sql and creates these main tables:
| Table | Purpose |
|---|---|
user |
Local and Google-authenticated accounts |
nut_values |
Per-user calorie and macro targets |
food |
User-defined ingredients |
meal |
Saved meals |
meal_food |
Ingredient rows belonging to a meal |
eaten_meal |
Meals logged for the current day |
eaten_history |
Saved daily nutrition history |
products |
Shared barcode and product lookup data |
The products table is created by the schema, but product data must be populated separately.
nutrition-app/
├── client/
│ ├── public/
│ │ └── wasm/ # Scanbot Web SDK assets
│ ├── src/
│ │ ├── components/ # UI components and modals
│ │ ├── context/ # React context providers
│ │ ├── countries/ # Country data for profile settings
│ │ ├── i18n/ # i18n setup and locale files
│ │ ├── pages/ # App and login pages
│ │ └── types/ # Shared frontend types
│ └── vite.config.ts # Vite + PWA configuration
├── database/
│ └── schema.sql # MySQL schema
├── server/
│ ├── src/
│ │ ├── config/ # Runtime config and rate limiters
│ │ ├── middleware/ # Token verification and error helpers
│ │ ├── routes/ # Express route modules
│ │ ├── auth.js # Auth router and Passport setup
│ │ ├── db.js # MySQL connection setup
│ │ └── routes.js # Protected API router aggregation
│ └── app.js # Express entry point
└── README.md
- The frontend currently ships with an English locale file in
client/src/i18n/locales/en.json. - There is no real automated test suite configured yet.
Provided as-is for portfolio and learning purposes.