Skip to content

Culinary Otter is a server-rendered Node.js app for discovering, favoriting, and customizing recipes with MySQL auth, sessions, Spoonacular integration, Kroger price lookups, and daily resets.

Notifications You must be signed in to change notification settings

r-siddiq/culinary-otter

Repository files navigation

Culinary Otter — Recipes, Custom Meals & Price Lookup (Node.js + Express + EJS + MySQL)

Node.js Express MySQL EJS Bootstrap

Overview

Culinary Otter is a server-rendered recipe helper that lets users discover dishes, favorite them, build custom recipes with structured ingredients, and estimate prices via a Kroger product lookup — all backed by MySQL with session-based auth and bcrypt-hashed passwords.

  • Discover: Random and detailed recipes powered by Spoonacular.
  • Favorite: Save/remove favorites tied to your account.
  • Customize: Create personal recipes (ingredients, instructions, dietary flags, image URL).
  • Price check: Cost lookups using Kroger’s OAuth + product APIs (default filter.locationId=01400943).
  • Accounts & roles: Registration, login, and role-based pages for user and admin.
  • Daily reset: The hosted site runs a daily cron that resets the database to a clean demo state.

Live demo: https://culinary-otter.r-siddiq.tech Additional context: https://www.rsiddiq.com/internet-programming.html


Tech Stack

  • Runtime: Node.js 18+
  • Web: Express 5, EJS templates, Bootstrap 5
  • DB: MySQL 8 (mysql2/promise pool)
  • Auth: express-session (cookie-based sessions), bcrypt password hashing
  • APIs: Spoonacular (recipes), Kroger (OAuth2 + product compact search)
  • Dotenv: environment-based configuration

Quick Start (Local)

1) Prerequisites

  • Node.js 18+ and npm
  • MySQL 8+ with a database you can import into
  • Shell with mysql client available (macOS/Linux or Git Bash/WSL on Windows)
  • API credentials: Spoonacular, Kroger

2) Clone & Install

git clone <this-repo-url>
cd culinary-otter
npm install

3) Configure Environment

Copy the example and add API keys + a strong SESSION_SECRET:

cp .env.example .env
# then edit .env

Required keys:

  • DB_HOST — MySQL hostname
  • DB_PORT — MySQL port (default 3306)
  • DB_USER — MySQL username
  • DB_PASSWORD — MySQL password
  • DB_NAME — MySQL database name
  • DB_CONNECTION_LIMIT — MySQL pool size (e.g. 10)
  • SPOON_API_KEY — Spoonacular API key
  • KROGER_CLIENT_ID — Kroger API OAuth client id
  • KROGER_CLIENT_SECRET — Kroger API OAuth client secret
  • SESSION_SECRET — Random string for signing sessions

For local DB imports, copy .my.cnf.example to .my.cnf in the project root and fill in your MySQL credentials. The restore script will read from that file.

4) Initialize / Reset the Database

Option A — scripted (recommended):

bash ./restore_db.sh

This drops existing tables (safely), imports CulinaryOtter_DB.sql, and seeds demo users/data.

Option B — manual:

mysql -u <USER> -p -h <HOST> -P <PORT> <DB_NAME> < CulinaryOtter_DB.sql

5) Run the App

node index.mjs
# App listens on http://localhost:3000

Optionally add "start": "node index.mjs" to package.json and run npm start.

Seeded demo accounts (local only):

  • admin / admin → Admin console
  • user / user → Standard user

The production demo resets daily via cron, so any changes there are ephemeral by design.


Database

Tables (InnoDB, UTF-8):

  • auth_users — users with role (admin|user), username, password (bcrypt), profile fields.
  • favorite_recipes — user favorites keyed by Spoonacular recipeId.
  • custom_recipes — user-authored recipes with ingredients (JSON), dietary flags, and free-form notes.

All schema & seeds live in CulinaryOtter_DB.sql. Use restore_db.sh to reset/import.


Project Structure

├── index.mjs                 # Express app (routes, auth, session, API calls)
├── CulinaryOtter_DB.sql      # MySQL schema + seed data
├── restore_db.sh             # Reset/import script (uses .my.cnf)
├── generate.js               # Utility to generate bcrypt hashes for seeds
├── views/                    # EJS templates (SSR)
│   ├── partials/{header,nav,footer}.ejs
│   └── *.ejs                 # Pages: login, register, index, favorites, custom recipes, admin, etc.
├── public/
│   ├── css/styles.css
│   ├── js/scripts.js
│   └── img/*.png|svg
├── .env.example              # DB defaults (copy to .env and add API keys + SESSION_SECRET)
├── .my.cnf.example           # Local MySQL client config for restore_db.sh
├── package.json
└── package-lock.json

Endpoints

Core pages

  • GET / — Home
  • GET /index — Landing feed
  • GET /about, GET /contact, GET /terms, GET /privacy — Static pages

Auth & Sessions

  • GET /login — Login page
  • POST /login — Body: username, password
  • GET /register — Registration page
  • POST /register — Body: username, firstName, lastName, password, confirmPassword
  • GET /logout — Destroy session
  • Portals: GET /welcomeUser, GET /welcomeAdmin

Recipes (Spoonacular)

  • Browse & details: GET /, GET /index, GET /recipeDetails/:id, GET /viewRecipeFull/:recipeId, GET /getRecipeDetails/:id
  • Search: GET /searchIngredients — query string parameters forwarded to Spoonacular
  • Favorites:
    • GET /favoriteRecipes
    • POST /favoriteRecipe — Body: recipeId (server fetches name/URL via Spoonacular)
    • POST /removeFavoriteRecipe — Body: favoriteId or recipeId

Custom Recipes

  • GET /customRecipes — List user’s custom recipes
  • GET /customizeRecipe/:id — Prefill form from a Spoonacular recipe
  • POST /customizeRecipe/:recipeId — Body:
    • recipeName, instructions, dishType, imageUrl
    • flags: vegetarian, vegan, glutenFree, dairyFree (truthy → 1)
    • ingredients — array-like payload; each item should provide { name, amount, unit }
      (client forms name inputs like ingredients[0][name], etc.)

Users (Admin)

  • GET /updateUser/:userId — Render edit form
  • POST /updateUser/:userId — Body: firstName, lastName, role (when admin), optional password, confirmPassword
  • POST /deleteUser/:userId — Remove a user

Utilities

  • GET /dbTest — DB connectivity check
  • GET /showTables, GET /showTableColumns — DB introspection
  • Kroger price lookup: GET /ingredientPrice?name=<term> — Requires Kroger OAuth configuration (getKrogerToken()); default filter.locationId=01400943

Server enables both express.urlencoded({ extended: true }) and express.json() to accept form and JSON payloads.


Authentication, Sessions, Encryption

  • Passwords: Hashed with bcrypt (see generate.js for seed hash generation).
  • Sessions: express-session with SESSION_SECRET. The code sets app.set('trust proxy', 1) for use behind a reverse proxy.
  • Authorization: Middleware guards (isAuthenticated, isUserOrAdmin, isAdmin) protect user/admin areas.
  • HTML sanitization: When Spoonacular returns HTML instructions, JSDOM is used to strip HTML to text for safer rendering.

Production Hardening

  • Session store: Configure a persistent store (Redis/MySQL) instead of the default in-memory store.
  • Secure cookies: Set cookie: { httpOnly: true, sameSite: 'lax', secure: true } when serving over HTTPS (and keep app.set('trust proxy', 1) behind proxies).
  • CSRF: Consider adding CSRF protection for form POSTs.
  • Rate limiting & headers: Add express-rate-limit and helmet.
  • Input validation: Validate body/query parameters (e.g., zod/joi/express-validator).

Deployment Notes

  • Provide .env with DB + API keys and a strong SESSION_SECRET.
  • Daily reset: A cron job executes restore_db.sh to reset the database to a demo baseline. Example (3:00 AM daily):
    0 3 * * * cd /var/www/culinary-otter && bash ./restore_db.sh >> /var/log/culinary-otter-reset.log 2>&1
  • Use a process manager (pm2/systemd). The app listens on port 3000 by default.

Development Tips

  • To change demo users, update the seed rows in CulinaryOtter_DB.sql or generate new password hashes:
    node generate.js
  • Client-side helpers live in public/js/scripts.js (favorite via fetch/AJAX, dynamic ingredient rows).
  • Layout/assets: views/partials and public/.

About

Culinary Otter is a server-rendered Node.js app for discovering, favoriting, and customizing recipes with MySQL auth, sessions, Spoonacular integration, Kroger price lookups, and daily resets.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published