├── .gitignore ├── README.md ├── public ├── img │ ├── black_shorthair.png │ ├── orange_longhair.png │ └── tabby_fireplace.png ├── css │ └── styles.css └── js │ └── script.js ├── views ├── partials │ └── header.ejs ├── item.ejs └── index.ejs ├── models └── Item.js ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fullstack_Template_EJS_MongoDB 2 | -------------------------------------------------------------------------------- /public/img/black_shorthair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayanwolfe/Fullstack_Template_EJS_MongoDB/HEAD/public/img/black_shorthair.png -------------------------------------------------------------------------------- /public/img/orange_longhair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayanwolfe/Fullstack_Template_EJS_MongoDB/HEAD/public/img/orange_longhair.png -------------------------------------------------------------------------------- /public/img/tabby_fireplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayanwolfe/Fullstack_Template_EJS_MongoDB/HEAD/public/img/tabby_fireplace.png -------------------------------------------------------------------------------- /views/partials/header.ejs: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /models/Item.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const ItemSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true 7 | }, 8 | description: { 9 | type: String, 10 | required: true 11 | } 12 | }); 13 | 14 | module.exports = mongoose.model('Item', ItemSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullstack_template_ejs_mongodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.3.1", 15 | "ejs": "^3.1.9", 16 | "express": "^4.18.2", 17 | "mongoose": "^7.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /views/item.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Item 5 | 6 | 7 | 8 | <%- include('./partials/header') %> 9 |

Item Page

10 | 11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | /* Navbar styling */ 2 | .navbar { 3 | background-color: #333; 4 | overflow: hidden; 5 | } 6 | 7 | /* Navbar list */ 8 | .nav-list { 9 | list-style-type: none; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | /* Navbar items */ 15 | .nav-item { 16 | float: left; 17 | } 18 | 19 | /* Navbar links */ 20 | .nav-link { 21 | display: block; 22 | color: white; 23 | text-align: center; 24 | padding: 14px 20px; 25 | text-decoration: none; 26 | } 27 | 28 | /* Change color on hover */ 29 | .nav-link:hover { 30 | background-color: #555; 31 | color: white; 32 | } 33 | 34 | 35 | body { 36 | font-family: Arial, sans-serif; 37 | line-height: 1.6; 38 | margin: 20px; 39 | padding: 0; 40 | } 41 | 42 | header { 43 | background-color: #f9f9f9; 44 | text-align: center; 45 | padding: 20px; 46 | } 47 | 48 | .sub-header { 49 | font-size: 1.2em; 50 | } 51 | 52 | h2 { 53 | background-color: #f0f0f0; 54 | padding: 10px; 55 | } 56 | 57 | ul, ol { 58 | margin-left: 20px; 59 | } 60 | 61 | blockquote { 62 | background-color: #f0f0f0; 63 | padding: 10px; 64 | margin: 20px 0; 65 | } 66 | 67 | footer { 68 | text-align: center; 69 | margin-top: 30px; 70 | } 71 | 72 | /* Carousel styling */ 73 | #carousel { 74 | width: 600px; 75 | height: 400px; 76 | overflow: hidden; 77 | margin: auto; 78 | } 79 | 80 | #carousel img { 81 | width: 100%; 82 | height: 100%; /* Changed from auto */ 83 | object-fit: contain; /* Make the image fit within the dimensions */ 84 | display: none; /* Hide images by default */ 85 | } 86 | 87 | #carousel img.active { 88 | display: block; /* Show active image */ 89 | } 90 | -------------------------------------------------------------------------------- /public/js/script.js: -------------------------------------------------------------------------------- 1 | function startCarousel() { 2 | let activeImage = 0; 3 | const images = document.querySelectorAll("#carousel img"); 4 | 5 | function cycleImages() { 6 | if (!images[activeImage]) { 7 | // If the active image is undefined, stop the interval and return 8 | clearInterval(intervalId); 9 | return; 10 | } 11 | //Loops the carousel 12 | images[activeImage].classList.remove("active"); 13 | activeImage = (activeImage + 1) % images.length; 14 | images[activeImage].classList.add("active"); 15 | } 16 | 17 | let intervalId = setInterval(cycleImages, 3000); 18 | } 19 | 20 | // Function to check for error in query parameters and show an alert 21 | function checkForError() { 22 | const urlParams = new URLSearchParams(window.location.search); 23 | if (urlParams.has('error')) { 24 | alert("Validation failed. Name and description are required."); 25 | } 26 | } 27 | 28 | // Start the carousel and check for errors when the page loads 29 | window.onload = function() { 30 | startCarousel(); 31 | checkForError(); 32 | }; 33 | 34 | function editItem(id, name, description) { 35 | // Populate the hidden field with the id 36 | document.getElementById("updateId").value = id; 37 | 38 | // Populate the form fields with the existing item's data 39 | document.getElementById("updateName").value = name; 40 | document.getElementById("updateDescription").value = description; 41 | 42 | // Update the form's action attribute 43 | document.getElementById("updateForm").action = `/item/update/${id}`; 44 | } 45 | 46 | async function deleteItem(id) { 47 | try { 48 | const response = await fetch(`http://localhost:3000/item/delete/${id}`, { 49 | method: 'DELETE', 50 | }); 51 | if (response.ok) { 52 | console.log('Item deleted successfully'); 53 | location.reload(); 54 | } else { 55 | console.log('Failed to delete item'); 56 | } 57 | } catch (error) { 58 | console.log('An error occurred:', error); 59 | } 60 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const mongoose = require('mongoose'); 4 | const cors = require('cors'); 5 | const Item = require('./models/Item') 6 | 7 | const app = express(); 8 | 9 | const port = process.env.PORT || 3000; 10 | 11 | // Connect to MongoDB - create a .env file and populate these environment variables with your database information. 12 | mongoose.connect(`mongodb+srv://${process.env.MONGODB_USERNAME}:${process.env.MONGODB_PASSWORD}@cluster0.pztfz.mongodb.net/${process.env.MONGODB_COLLECTION_NAME}?retryWrites=true&w=majority`) 13 | .then(() => console.log('MongoDB connected')) 14 | .catch(err => console.log(err)); 15 | 16 | // Enable CORS 17 | app.use(cors()); 18 | 19 | // Serve static files 20 | app.use(express.static('public')); 21 | 22 | // Parse requests for strings/arrays, use extended: true for nested requests 23 | app.use(express.urlencoded({ extended: false })); 24 | 25 | //Parse JSON requests 26 | app.use(express.json()) 27 | 28 | // Set EJS as templating engine 29 | app.set('view engine', 'ejs'); 30 | 31 | // Routes 32 | app.get('/', (req, res) => { 33 | res.render('index') 34 | }); 35 | 36 | 37 | app.get('/item', async (req, res) => { 38 | const items = await Item.find({}); 39 | res.render('item', { items }); 40 | }); 41 | 42 | // Create 43 | app.post('/item', async (req, res) => { 44 | const newItem = new Item(req.body); 45 | try { 46 | await newItem.save(); 47 | res.redirect('/item'); 48 | } catch (err) { 49 | //you can set up custom error handling on the client side using this information 50 | res.redirect('/item?error=true'); 51 | } 52 | }); 53 | 54 | // Update 55 | app.post('/item/update/:id', async (req, res) => { 56 | const { id } = req.params; 57 | const { name, description } = req.body; 58 | try { 59 | await Item.findByIdAndUpdate(id, { name, description }); 60 | res.redirect('/item'); 61 | } catch (err) { 62 | //you can set up custom error handling on the client side using this information 63 | res.redirect(`/item?error=true`); 64 | } 65 | }); 66 | 67 | // Delete 68 | app.delete('/item/delete/:id', async (req, res) => { 69 | const { id } = req.params; 70 | await Item.findByIdAndDelete(id); 71 | //Send success back to the client-side function 72 | res.status(200).json({ message: 'Item deleted successfully' }); 73 | }); 74 | 75 | 76 | // Start the server 77 | app.listen(port, () => { 78 | console.log(`Server running on http://localhost:${port}`); 79 | }); 80 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | 7 | 8 |
9 | <%- include('./partials/header') %> 10 |

Welcome to KittenLand!

11 |

🐾 Your Ultimate Source for All Things Kitten! 🐾

12 |
13 | 14 | 15 | 21 | All images generated by DALL-E 3 22 |
23 |

Why Kittens?

24 |

Kittens are more than just adorable balls of fluff; they are tiny, playful, and inquisitive companions that bring joy and happiness to any household. Whether you're a first-time kitten owner or a seasoned feline enthusiast, you've come to the right place!

25 |
26 | 27 |
28 |

What We Offer

29 | 35 |
36 | 37 |
38 |

Latest Blog Posts

39 |
    40 |
  1. "The First 30 Days with Your New Kitten"
  2. 41 |
  3. "How to Kitten-Proof Your Home"
  4. 42 |
  5. "Best DIY Toys for Kittens"
  6. 43 |
44 |
45 | 46 |
47 |

Testimonials

48 |
49 |

"KittenLand is my go-to resource for everything kitten-related. The care guides are incredibly helpful!"

50 |
— Jane D., Proud Kitten Owner
51 |
52 |
53 |

"I adopted my first kitten last month, and the tips on this website have been a lifesaver!"

54 | 55 |
56 |
57 | 58 |
59 |

Get Involved

60 |

Join our community of kitten lovers! Follow us on social media, subscribe to our newsletter, or contribute to our blog. We'd love to have you as part of the KittenLand family!

61 |
62 | 63 | 68 | 69 | 70 | 71 | 72 | --------------------------------------------------------------------------------