From 8766d87a3c1debe7767f24a3ba67df9a32608fab Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 12 Mar 2026 17:08:00 +0100 Subject: [PATCH] 1.0 --- README.md | 65 +++ package.json | 12 + public/index.html | 874 ++++++++++++++++++++++++++++++++ recipes/sample-lemon-pasta.json | 33 ++ server.js | 104 ++++ 5 files changed, 1088 insertions(+) create mode 100644 README.md create mode 100644 package.json create mode 100644 public/index.html create mode 100644 recipes/sample-lemon-pasta.json create mode 100644 server.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ffc0b6 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# 📖 The Recipe Box + +A gorgeous, minimal cookbook website that stores your recipes as JSON files locally. + +## ✨ Features + +- **Beautiful UI** — Warm, editorial design inspired by vintage recipe cards +- **JSON Storage** — All recipes saved as individual `.json` files in the `recipes/` folder +- **Full CRUD** — Add, view, edit, and delete recipes +- **Zero Database** — Everything is file-based, easy to backup and version control + +## 🚀 Quick Start + +1. **Install dependencies** + ```bash + npm install + ``` + +2. **Start the server** + ```bash + npm start + ``` + +3. **Open your browser** + ``` + http://localhost:3000 + ``` + +That's it! Your cookbook is ready. 🍳 + +## 📁 Recipe Format + +Recipes are stored as JSON files in the `recipes/` folder: + +```json +{ + "id": "unique-recipe-id", + "title": "Recipe Name", + "category": "Dinner", + "description": "A brief description", + "servings": "4", + "prepTime": "15 mins", + "cookTime": "30 mins", + "ingredients": [ + "First ingredient", + "Second ingredient" + ], + "instructions": [ + "Step one", + "Step two" + ], + "createdAt": "2025-03-12T10:00:00.000Z", + "updatedAt": "2025-03-12T10:00:00.000Z" +} +``` + +## 💡 Tips + +- **Backup recipes**: Just copy the entire `recipes/` folder +- **Share recipes**: Send individual `.json` files to friends +- **Version control**: Track your recipe evolution with git! + +--- + +Made with 🧈 and ❤️ diff --git a/package.json b/package.json new file mode 100644 index 0000000..b898cd3 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "the-recipe-box", + "version": "1.0.0", + "description": "A beautiful cookbook website for your culinary treasures", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..f71aef7 --- /dev/null +++ b/public/index.html @@ -0,0 +1,874 @@ + + + + + + The Recipe Box + + + + +
+

The Recipe Box

+

A collection of culinary treasures

+
+ +
+
+

0 recipes in your collection

+ +
+ +
+ +
+
+ + + + + + + + +
+ + + + diff --git a/recipes/sample-lemon-pasta.json b/recipes/sample-lemon-pasta.json new file mode 100644 index 0000000..27217b2 --- /dev/null +++ b/recipes/sample-lemon-pasta.json @@ -0,0 +1,33 @@ +{ + "id": "sample-lemon-pasta", + "title": "Creamy Lemon Pasta", + "category": "Dinner", + "description": "A bright, zesty pasta that comes together in under 30 minutes. Perfect for busy weeknights when you want something that feels fancy but isn't fussy.", + "servings": "4", + "prepTime": "10 mins", + "cookTime": "15 mins", + "ingredients": [ + "400g spaghetti or linguine", + "3 tablespoons butter", + "2 cloves garlic, minced", + "Zest of 2 lemons", + "Juice of 1 lemon", + "1 cup heavy cream", + "¾ cup freshly grated Parmesan", + "Salt and black pepper to taste", + "Fresh basil or parsley for garnish", + "Red pepper flakes (optional)" + ], + "instructions": [ + "Bring a large pot of salted water to boil. Cook pasta according to package directions until al dente. Reserve 1 cup pasta water before draining.", + "While pasta cooks, melt butter in a large skillet over medium heat. Add garlic and cook for 1 minute until fragrant.", + "Add lemon zest and cook for another 30 seconds, stirring constantly.", + "Pour in the heavy cream and bring to a gentle simmer. Let it reduce slightly for 2-3 minutes.", + "Add the drained pasta to the skillet along with lemon juice and half the Parmesan. Toss to combine.", + "Add pasta water a splash at a time until you reach your desired sauce consistency.", + "Remove from heat, add remaining Parmesan, and season with salt and pepper.", + "Serve immediately, garnished with fresh herbs and red pepper flakes if desired." + ], + "createdAt": "2025-03-12T10:00:00.000Z", + "updatedAt": "2025-03-12T10:00:00.000Z" +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..6457ba7 --- /dev/null +++ b/server.js @@ -0,0 +1,104 @@ +const express = require('express'); +const fs = require('fs').promises; +const path = require('path'); + +const app = express(); +const PORT = 3000; +const RECIPES_DIR = path.join(__dirname, 'recipes'); + +app.use(express.json()); +app.use(express.static('public')); + +// Ensure recipes directory exists +async function ensureRecipesDir() { + try { + await fs.access(RECIPES_DIR); + } catch { + await fs.mkdir(RECIPES_DIR, { recursive: true }); + } +} + +// GET all recipes +app.get('/api/recipes', async (req, res) => { + try { + await ensureRecipesDir(); + const files = await fs.readdir(RECIPES_DIR); + const recipes = []; + + for (const file of files) { + if (file.endsWith('.json')) { + const content = await fs.readFile(path.join(RECIPES_DIR, file), 'utf-8'); + recipes.push(JSON.parse(content)); + } + } + + res.json(recipes); + } catch (error) { + res.status(500).json({ error: 'Failed to load recipes' }); + } +}); + +// GET single recipe by id +app.get('/api/recipes/:id', async (req, res) => { + try { + const filePath = path.join(RECIPES_DIR, `${req.params.id}.json`); + const content = await fs.readFile(filePath, 'utf-8'); + res.json(JSON.parse(content)); + } catch (error) { + res.status(404).json({ error: 'Recipe not found' }); + } +}); + +// POST new recipe +app.post('/api/recipes', async (req, res) => { + try { + await ensureRecipesDir(); + const recipe = req.body; + + // Generate ID if not provided + if (!recipe.id) { + recipe.id = Date.now().toString(36) + Math.random().toString(36).substr(2); + } + + recipe.createdAt = recipe.createdAt || new Date().toISOString(); + recipe.updatedAt = new Date().toISOString(); + + const filePath = path.join(RECIPES_DIR, `${recipe.id}.json`); + await fs.writeFile(filePath, JSON.stringify(recipe, null, 2)); + + res.status(201).json(recipe); + } catch (error) { + res.status(500).json({ error: 'Failed to save recipe' }); + } +}); + +// PUT update recipe +app.put('/api/recipes/:id', async (req, res) => { + try { + const recipe = req.body; + recipe.id = req.params.id; + recipe.updatedAt = new Date().toISOString(); + + const filePath = path.join(RECIPES_DIR, `${recipe.id}.json`); + await fs.writeFile(filePath, JSON.stringify(recipe, null, 2)); + + res.json(recipe); + } catch (error) { + res.status(500).json({ error: 'Failed to update recipe' }); + } +}); + +// DELETE recipe +app.delete('/api/recipes/:id', async (req, res) => { + try { + const filePath = path.join(RECIPES_DIR, `${req.params.id}.json`); + await fs.unlink(filePath); + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: 'Failed to delete recipe' }); + } +}); + +app.listen(PORT, () => { + console.log(`🍳 Cookbook server running at http://localhost:${PORT}`); +});