The Palos Publishing Company

Follow Us On The X Platform @PalosPublishing
Categories We Write About

Create a recipe database with tagging features

Here’s a production-ready implementation of a recipe database with tagging features using React (frontend) and Node.js + Express (backend) with a MongoDB database. The app allows users to add, edit, and retrieve recipes, each associated with tags for filtering and categorization.


Backend: Node.js + Express + MongoDB

1. models/Recipe.js

javascript
const mongoose = require('mongoose'); const recipeSchema = new mongoose.Schema({ title: { type: String, required: true }, ingredients: [String], instructions: String, tags: [String], createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('Recipe', recipeSchema);

2. routes/recipes.js

javascript
const express = require('express'); const router = express.Router(); const Recipe = require('../models/Recipe'); // Create new recipe router.post('/', async (req, res) => { try { const recipe = new Recipe(req.body); await recipe.save(); res.status(201).json(recipe); } catch (err) { res.status(400).json({ error: err.message }); } }); // Get all recipes or filter by tags router.get('/', async (req, res) => { const { tags } = req.query; const query = tags ? { tags: { $in: tags.split(',') } } : {}; const recipes = await Recipe.find(query); res.json(recipes); }); // Get recipe by ID router.get('/:id', async (req, res) => { try { const recipe = await Recipe.findById(req.params.id); res.json(recipe); } catch { res.status(404).json({ error: 'Recipe not found' }); } }); // Update recipe router.put('/:id', async (req, res) => { try { const updated = await Recipe.findByIdAndUpdate(req.params.id, req.body, { new: true }); res.json(updated); } catch { res.status(400).json({ error: 'Update failed' }); } }); // Delete recipe router.delete('/:id', async (req, res) => { await Recipe.findByIdAndDelete(req.params.id); res.json({ success: true }); }); module.exports = router;

3. server.js

javascript
const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const recipesRoutes = require('./routes/recipes'); const app = express(); app.use(cors()); app.use(express.json()); mongoose.connect('mongodb://localhost:27017/recipeDB', { useNewUrlParser: true, useUnifiedTopology: true }); app.use('/api/recipes', recipesRoutes); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Frontend: React with Tailwind and Axios

1. Install dependencies

bash
npm install axios react-router-dom

2. App.js

javascript
import React from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import RecipeList from './components/RecipeList'; import RecipeForm from './components/RecipeForm'; function App() { return ( <Router> <div className="p-4 max-w-4xl mx-auto"> <h1 className="text-3xl font-bold mb-4">Recipe Manager</h1> <Routes> <Route path="/" element={<RecipeList />} /> <Route path="/new" element={<RecipeForm />} /> </Routes> </div> </Router> ); } export default App;

3. components/RecipeList.js

javascript
import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { Link } from 'react-router-dom'; function RecipeList() { const [recipes, setRecipes] = useState([]); const [tagFilter, setTagFilter] = useState(''); useEffect(() => { fetchRecipes(); }, [tagFilter]); const fetchRecipes = async () => { const query = tagFilter ? `?tags=${tagFilter}` : ''; const res = await axios.get(`http://localhost:5000/api/recipes${query}`); setRecipes(res.data); }; return ( <div> <div className="mb-4 flex gap-2"> <input type="text" placeholder="Filter by tag" value={tagFilter} onChange={(e) => setTagFilter(e.target.value)} className="border p-2 rounded" /> <Link to="/new" className="bg-blue-500 text-white px-4 py-2 rounded">Add Recipe</Link> </div> <ul> {recipes.map((recipe) => ( <li key={recipe._id} className="mb-4 border p-4 rounded shadow"> <h2 className="text-xl font-bold">{recipe.title}</h2> <p className="text-gray-700">{recipe.instructions}</p> <div className="mt-2"> {recipe.tags.map(tag => ( <span key={tag} className="inline-block bg-gray-200 px-2 py-1 text-sm mr-1 rounded"> #{tag} </span> ))} </div> </li> ))} </ul> </div> ); } export default RecipeList;

4. components/RecipeForm.js

javascript
import React, { useState } from 'react'; import axios from 'axios'; import { useNavigate } from 'react-router-dom'; function RecipeForm() { const [form, setForm] = useState({ title: '', ingredients: '', instructions: '', tags: '' }); const navigate = useNavigate(); const handleChange = (e) => { setForm({...form, [e.target.name]: e.target.value}); }; const handleSubmit = async (e) => { e.preventDefault(); const payload = { ...form, ingredients: form.ingredients.split(',').map(i => i.trim()), tags: form.tags.split(',').map(t => t.trim()) }; await axios.post('http://localhost:5000/api/recipes', payload); navigate('/'); }; return ( <form onSubmit={handleSubmit} className="space-y-4"> <input name="title" value={form.title} onChange={handleChange} placeholder="Title" required className="w-full border p-2 rounded" /> <textarea name="ingredients" value={form.ingredients} onChange={handleChange} placeholder="Ingredients (comma-separated)" className="w-full border p-2 rounded" /> <textarea name="instructions" value={form.instructions} onChange={handleChange} placeholder="Instructions" className="w-full border p-2 rounded" /> <input name="tags" value={form.tags} onChange={handleChange} placeholder="Tags (comma-separated)" className="w-full border p-2 rounded" /> <button type="submit" className="bg-green-600 text-white px-4 py-2 rounded">Save Recipe</button> </form> ); } export default RecipeForm;

Summary Features

  • Add/Edit/Delete recipes

  • Tagging system for filtering (e.g. ?tags=vegan,gluten-free)

  • MongoDB for flexible schema management

  • Express API with RESTful endpoints

  • React frontend with form and list components

  • Axios for API requests

  • Tailwind CSS for minimal styling

Let me know if you’d like features like authentication, ratings, or image uploads added.

Share this Page your favorite way: Click any app below to share.

Enter your email below to join The Palos Publishing Company Email List

We respect your email privacy

Categories We Write About