Save social recipe videos as a personal cookbook, then ask an AI assistant for recipe-specific help.
Joyi is a full-stack recipe collection app built around a simple idea: paste a recipe link from TikTok, Instagram, or Facebook, let automation extract the useful cooking data, and keep the result in a clean private dashboard.
The project is split into a React/Vite frontend, an Express/TypeScript backend, a MySQL database layer, and n8n workflows that handle recipe scraping and AI responses.
Live demo: joyi-project.web.app
The public landing page opens with a bold hero — "Save Every Recipe You Love" — and three floating feature callouts overlaid on a food photography backdrop: Save from any platform, AI does the magic, and Your recipes, your way. The top navigation bar keeps things minimal with Home, Login, and a prominent Get Started CTA. Social proof sits below the hero copy with a 4.9-star rating and a row of avatar icons, establishing trust before the user signs up.
New users land on a clean, centered registration layout. The left column sets the tone — "Create your recipe collection" with a short tagline — while the form card in the center collects first name, last name, email, and password under the Joyi logo. A decorative cookbook illustration on the right reinforces the personal-cookbook concept. Existing users can jump straight to login via the Already saving recipes? Login link at the bottom.
The main workspace for every user. A persistent sidebar holds navigation links (Dashboard, Favorites, Collections, Profile, What is Joyi?), a Upgrade to Premium prompt, and the signed-in account badge. The content area opens with a personalised greeting and a recipe count at a glance. A single input bar accepts TikTok, Instagram, and Facebook links — paste a URL and hit Analyze Recipe to kick off the scraping workflow. Saved recipes appear below as cards, each showing the source platform badge, a polaroid-style thumbnail, recipe name, and a full macro summary (calories, protein, carbs, fats, servings). Platform filter chips and a search bar sit above the grid for quick browsing.
Opening any recipe card expands into a full detail view. The page leads with the recipe title in a large serif heading, a polaroid thumbnail, and at-a-glance metadata — platform badge, serving count, and total calories. A short summary paragraph sits below. Three side-by-side panels fill the lower half: a Nutrition card with a Total Recipe / Per Serving toggle and animated macro progress bars, an Ingredients list, and numbered Instructions steps. A floating Joy launcher in the bottom-right corner shows a welcome tip and opens the AI assistant with a single click.
Joy is Joyi's built-in recipe assistant, accessible from every recipe page. The chat panel opens with Joy's avatar and a context-aware greeting tied to the current recipe. Users can type any question or tap a suggested quick-reply chip — How can I make this healthier?, What can I substitute? — and Joy responds with recipe-specific advice powered by the n8n AI workflow. The input field at the bottom accepts freeform questions about swaps, prep techniques, macros, and more.
Both automation pipelines live entirely in n8n. The Recipe Scraping workflow (top) starts at the ScrapeRecipe Webhook, scrapes the social page, routes the raw HTML through a platform Switch node into separate TikTok, Instagram, and Facebook caption extractors, validates the output with an If gate, then runs two sequential AI steps — one for the recipe summary and one for nutrition extraction — before reformatting everything into a single JSON object and returning it via Respond to Webhook. The AI Assistant workflow (bottom) is leaner: the Askai Webhook feeds the user's question and recipe context into an AI Agent node backed by an OpenAI Chat Model with Simple Memory, then responds directly.
- Saves recipes from social links.
- Supports TikTok, Instagram, and Facebook recipe sources.
- Extracts recipe title, ingredients, instructions, servings, thumbnail, calories, and macro nutrition.
- Stores every recipe per authenticated user.
- Provides login/register flows with JWT authentication.
- Shows a searchable, filterable recipe dashboard.
- Opens a detailed recipe page with nutrition tabs, ingredients, instructions, and the original video link.
- Includes Joy, an AI recipe assistant that answers questions using the saved recipe context.
- Uses n8n webhooks as the automation layer between the app and external scraping / AI workflows.
Joyi/
├── Backend/
│ ├── src/
│ │ ├── 2-utils/ # config, database access, crypto, n8n parsing
│ │ ├── 3-models/ # Joi-backed models and typed domain objects
│ │ ├── 4-services/ # business logic for users, recipes, and AI
│ │ ├── 5-controllers/ # Express route controllers
│ │ ├── 6-middleware/ # auth, XSS stripping, error handling
│ │ └── app.ts # Express app bootstrap
│ ├── package.json
│ └── tsconfig.json
│
├── Frontend/
│ ├── src/
│ │ ├── Components/ # pages, cards, layout, assistant UI
│ │ ├── Models/ # frontend TypeScript models
│ │ ├── Services/ # API clients
│ │ ├── Utils/ # app config, auth headers, notifications
│ │ ├── store/ # lightweight auth store
│ │ └── main.tsx
│ ├── package.json
│ └── vite.config.ts
│
└── Database/ # reserved for database scripts / exports
Frontend
- React 19
- TypeScript
- Vite
- React Router
- Axios
- React Hook Form
- Notyf notifications
- Lucide React icons
Backend
- Node.js
- Express 5
- TypeScript
- MySQL2
- Joi validation
- JWT authentication
- HMAC SHA-512 password hashing
- n8n webhook integration
Automation
- n8n recipe scraping webhook
- n8n AI assistant webhook
- Backend response normalization for inconsistent n8n webhook output
- A user registers or logs in.
- The frontend stores the JWT in
localStorage. - The dashboard sends authenticated requests with
Authorization: Bearer <token>. - The user pastes a TikTok, Instagram, or Facebook recipe link.
- The backend sends that link to the configured n8n scraping webhook.
- n8n returns normalized recipe data.
- The backend validates and saves the recipe in MySQL.
- The frontend displays the recipe in the dashboard.
- The user can open the recipe page and ask Joy questions.
- Joy sends the question plus recipe context to a second n8n AI webhook.
The backend runs from Backend/src/app.ts and listens on the port configured in .env.
cd Backend
npm install
npm startnpm start runs:
nodemon --exec ts-node src/app.ts --quietCreate Backend/.env:
ENVIRONMENT=development
PORT=4000
MYSQL_HOST=localhost
MYSQL_USER=root
MYSQL_PASSWORD=your_mysql_password
MYSQL_DATABASE=joyi
HASH_SALT=replace_with_a_long_random_salt
JWT_SECRET=replace_with_a_long_random_secret
N8N_WEBHOOK_URL=https://your-n8n-domain/webhook/recipe-scraper
N8N_ASK_AI_WEBHOOK_URL=https://your-n8n-domain/webhook/ask-recipe-aiDo not commit real secrets.
| Method | Route | Auth | Purpose |
|---|---|---|---|
POST |
/api/register |
No | Creates a user and returns a JWT |
POST |
/api/login |
No | Logs in and returns a JWT |
POST |
/api/recipes/scrape |
Yes | Sends a social recipe URL to n8n, saves the returned recipe |
GET |
/api/recipes |
Yes | Returns the logged-in user's saved recipes |
GET |
/api/recipes/:id |
Yes | Returns one recipe owned by the logged-in user |
DELETE |
/api/recipes/:id |
Yes | Deletes one recipe owned by the logged-in user |
POST |
/api/recipes/ask |
Yes | Sends a recipe question to the n8n AI workflow |
- Passwords are hashed with HMAC SHA-512 using
HASH_SALT. - JWTs expire after 3 hours.
- Protected recipe routes verify the JWT.
- Recipe reads and deletes are scoped by
userId. - Incoming string fields are passed through
striptagsto reduce XSS risk. - Joi models validate auth, recipe, and AI assistant payloads.
The frontend is a Vite React app configured to talk to:
http://localhost:4000/apiThis is defined in Frontend/src/Utils/AppConfig.ts.
cd Frontend
npm install
npm run devFor production build:
npm run build| Route | Purpose |
|---|---|
/ and /landing |
Public landing page |
/login |
Login form |
/register |
Registration form |
/dashboard |
Main saved-recipe dashboard |
/recipe/:id |
Detailed recipe view with Joy assistant |
/favorites |
Placeholder / coming feature area |
/collections |
Placeholder / coming feature area |
/history |
Placeholder / coming feature area |
/profile |
Placeholder / coming feature area |
/settings |
Placeholder / coming feature area |
- Protected routes redirect logged-out users to
/login. - Public auth routes redirect logged-in users to
/dashboard. - Dashboard supports paste-from-clipboard, platform filtering, search, save, and delete.
- Recipe pages show image, platform badge, servings, nutrition totals/per-serving, ingredients, steps, and original video CTA.
- Joy assistant opens as a recipe-specific chat panel with suggested questions and animated answers.
The backend expects a MySQL database. The Database/ folder contains JoyiDB.sql with the full schema. To set up locally, import it into MySQL:
mysql -u root -p < Database/JoyiDB.sqlThe schema includes at least:
create database if not exists joyi;
use joyi;
create table users (
userId int primary key auto_increment,
firstName varchar(50) not null,
lastName varchar(50) not null,
email varchar(255) not null unique,
password varchar(255) not null,
role varchar(20) not null
);
create table recipes (
recipeId int primary key auto_increment,
userId int not null,
title varchar(255) not null,
linkUrl varchar(1000) not null,
platform enum('tiktok', 'instagram', 'facebook') not null,
ingredients json not null,
instructions text null,
servings varchar(50) null,
thumbnail varchar(1000) null,
totalCalories decimal(10,2) null,
caloriesPerServing decimal(10,2) null,
protein decimal(10,2) null,
carbs decimal(10,2) null,
fats decimal(10,2) null,
proteinPerServing decimal(10,2) null,
carbsPerServing decimal(10,2) null,
fatsPerServing decimal(10,2) null,
savedAt timestamp not null default current_timestamp,
foreign key (userId) references users(userId)
);The backend stores ingredients as JSON text and safely parses it when reading recipes back from the database.
n8n is the automation heart of this project. The backend does not scrape social platforms directly and does not call an AI model directly. Instead, it calls two external n8n webhooks.
Configured by:
N8N_WEBHOOK_URL=...Called from:
Backend/src/4-services/recipe-service.ts
Backend request sent to n8n:
{
"linkUrl": "https://www.tiktok.com/..."
}Expected normalized n8n response:
{
"title": "Roasted Tomato Rigatoni",
"linkUrl": "https://www.tiktok.com/...",
"platform": "tiktok",
"ingredients": ["tomatoes", "rigatoni", "garlic"],
"instructions": "Step 1...\nStep 2...",
"servings": "2",
"thumbnail": "https://...",
"totalCalories": 800,
"caloriesPerServing": 400,
"protein": 30,
"carbs": 110,
"fats": 25,
"proteinPerServing": 15,
"carbsPerServing": 55,
"fatsPerServing": 12.5
}The backend accepts n8n responses as a plain object, an array, or an n8n { json: ... } envelope. It also handles junk prefixes such as null{...} through Backend/src/2-utils/n8n-parser.ts.
The workflow should:
- Receive
linkUrl. - Detect or return the source platform.
- Extract title, thumbnail, ingredients, instructions, servings, calories, and macros.
- Return one clean recipe object through the n8n Respond to Webhook node.
Configured by:
N8N_ASK_AI_WEBHOOK_URL=...Called from:
Backend/src/4-services/ai-service.ts
Backend request sent to n8n:
{
"question": "Can I meal prep this?",
"recipe": {
"recipeId": 12,
"title": "Protein Pancakes",
"ingredients": ["eggs", "banana", "protein powder"],
"instructions": "Mix and cook.",
"servings": "2",
"caloriesPerServing": 270,
"proteinPerServing": 16,
"carbsPerServing": 30,
"fatsPerServing": 8
}
}Accepted n8n response keys:
{ "text": "Yes, this works well for meal prep..." }or:
{ "answer": "Yes, this works well for meal prep..." }or:
{ "output": "Yes, this works well for meal prep..." }The workflow should:
- Receive a user question and recipe context.
- Pass both into an AI model node.
- Return a concise helpful answer using
text,answer, oroutput. - Avoid returning unrelated metadata as the final webhook response.
- Start MySQL and create the database/tables.
- Create
Backend/.envwith database, JWT, hash salt, and n8n webhook values. - Start the backend:
cd Backend
npm install
npm start- Start the frontend in a second terminal:
cd Frontend
npm install
npm run dev- Open the Vite URL shown in the terminal, usually:
http://localhost:5173
A saved recipe looks like:
interface RecipeModel {
recipeId: number;
userId: number;
title: string;
linkUrl: string;
platform: "tiktok" | "instagram" | "facebook";
ingredients: string[];
instructions: string;
servings: string;
thumbnail: string;
totalCalories: number;
caloriesPerServing: number;
protein: number;
carbs: number;
fats: number;
proteinPerServing: number;
carbsPerServing: number;
fatsPerServing: number;
savedAt: Date;
}Database/JoyiDB.sqlcontains the full MySQL schema.Backend/.env.exampledocuments the required local environment variables.- The n8n workflows are required for the core save-recipe and ask-Joy features to work.
- The frontend is deployed on Firebase at joyi-project.web.app; the backend must be running separately for data features to function.
- Add automated tests for the n8n parser and service validation.
- Add request rate limiting around auth and n8n webhook routes.
- Deploy the backend and configure production environment variables so the live Firebase frontend is fully functional.





