1. /app.js
// Load environment variables from .env file
require('dotenv').config({ path: './.env' });
// Import required modules
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// Import route handlers
var indexRouter = require('./routes/index.routes');
var userRouter = require('./routes/user.routes');
// Create an Express application
var app = express();
// Set up view engine
app.set('views', path.join(__dirname, 'views')); // Set views directory
app.set('view engine', 'ejs'); // Set view engine to EJS
// Database connection
const db = require('./models/db');
db.connectDB(); // Connect to the database
// Import user schema/model
const UserCollection = require('./models/user.schema');
// ------------- passport & session config -------------
const session = require('express-session');
const passport = require('passport');
// Configure session middleware
app.use(
session({
secret: process.env.SESSION_SECRET, // Session secret from environment variables
resave: false, // Do not save session if unmodified
saveUninitialized: true // Save uninitialized sessions
})
);
// Initialize passport and session
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(UserCollection.serializeUser()); // Serialize user instance
passport.deserializeUser(UserCollection.deserializeUser()); // Deserialize user instance
// ------------- passport & session config -------------
// Middleware setup
app.use(logger('dev')); // Log requests to the console
app.use(express.json()); // Parse JSON request bodies
app.use(express.urlencoded({ extended: false })); // Parse URL-encoded request bodies
app.use(cookieParser()); // Parse cookies
app.use(express.static(path.join(__dirname, 'public'))); // Serve static files from the public directory
// Use routes
app.use('/', indexRouter);
app.use('/user', userRouter);
// Catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404)); // Forward to error handler with 404 status
});
// Error handler
app.use(function(err, req, res, next) {
// Set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// Render the error page
res.status(err.status || 500);
res.render('error'); // Render error view
});
// Export the app module
module.exports = app;
2. Mongo DB
๐ ./models/db.js
// Import the mongoose library
const mongoose = require('mongoose');
// Export an asynchronous function to connect to the MongoDB database
exports.connectDB = async () => {
try {
// Attempt to connect to the database using the URI from environment variables
const conn = await mongoose.connect(process.env.MONGO_URI);
// Log a success message with the host of the connection
console.log("DB Connected : ", conn.connection.host);
} catch (err) {
// Log an error message if the connection fails
console.error("Connection Error : ", err.message);
}
};
๐ ./models/user.schema.js
// Import the mongoose library
const mongoose = require('mongoose');
// Import passport-local-mongoose plugin
const plm = require('passport-local-mongoose');
// Define a new schema for the user collection
const user_schema = mongoose.Schema({
email: {
type: String,
required: [true, "Book Name is Required!"], // Field is required with a custom error message
minLength: [4, "Book Name must have minimum 4 characters"], // Minimum length of 4 characters with a custom error message
trim: true, // Remove whitespace from both ends of the string
// uppercase: true, // Uncomment to force the string to uppercase
// lowercase: true, // Uncomment to force the string to lowercase
// maxLength: [15, "Max Book Length is Reached."], // Uncomment to set a maximum length with a custom error message
// unique: true, // Uncomment to ensure the value is unique
// select: true, // Uncomment to include the field in query results
// default: "Demo Text", // Uncomment to set a default value
// match: [/regex/, "invalid msg"] // Uncomment to validate the string with a regular expression and custom error message
},
username: {
type: String,
required: true // Field is required
},
password: String, // Password field as a string
avatar: {
type: String,
default: "https://w7.pngwing.com/pngs/1000/665/png-transparent-computer-icons-profile-s-free-angle-sphere-profile-cliparts-free-thumbnail.png" // Default avatar URL
}
}, { timestamps: true } // Automatically adds createdAt and updatedAt timestamps
);
// Apply the passport-local-mongoose plugin to the schema
user_schema.plugin(plm);
// Create a model named "social-media" using the user schema
const User_Collection = mongoose.model("social-media", user_schema);
// Log a message indicating the schema has been created
console.log("Schema Created");
// Export the user collection model
module.exports = User_Collection;
3. Multer: for uploading image file
๐ ./views/createBook.js
<form action="/create-book" enctype="multipart/form-data" method="post" id="createBook" class="d-flex gap-4 my-5 w-100 flex-column">
<!-- <input type="url" class="w-50" name="poster" id="" placeholder="Book Poster Link ๐"> -->
<input type="file" class="w-50" name="poster" id="" placeholder="Book Poster Link ๐">
<button class="mycard-button btn px-5 w-50" style="font-size: 1.2rem;">Add to Library</button>
</form>
๐ ./utils/multer.js
// Import multer for handling file uploads
const multer = require('multer');
// Import path module for handling file paths
const path = require("path");
// Configure storage options for multer
const storage = multer.diskStorage({
// Set the destination folder for uploaded files
destination: (req, file, cb) => {
// Log the file details (uncomment if needed)
// console.log(file);
cb(null, "public/images"); // Store files in the 'public/images' directory
},
// Set the filename for uploaded files
filename: (req, file, cb) => {
// Create a unique filename using the current timestamp and the original file extension
const modifiedFileName = Date.now() + path.extname(file.originalname);
cb(null, modifiedFileName); // Save the file with the new name
}
});
// Filter files by type
const fileFilter = function (req, file, cb) {
// Define allowed file types
const filetypes = /jpeg|jpg|png|gif/;
// Check the file's mimetype against the allowed types
const mimetype = filetypes.test(file.mimetype);
// Check the file's extension against the allowed types
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Accept the file if both mimetype and extension are valid
if (mimetype && extname) {
return cb(null, true);
} else {
// Reject the file and return an error if the type is invalid
cb(new Error("Error: Images Only"));
}
// Log the mimetype check result (uncomment if needed)
// console.log("mimetype: ", mimetype);
};
// Configure multer with the defined storage options and file filter
const upload = multer({
storage: storage,
fileFilter: fileFilter
});
// Export the configured multer instance for use in other parts of the application
module.exports = upload;
๐ ./routes/index.route.js
var express = require('express');
var router = express.Router();
const BookCollection = require('../models/bookModel');
const upload = require('../utils/multer');
const fs = require('fs');
const path = require('path');
// ๐ -------- CREATE operation - handle form submission -------- ๐
router.post('/create-book', upload.single('poster'), async function (req, res, next) {
try {
const newBook = await new BookCollection({ ...req.body, poster: req.file.filename });
await newBook.save();
console.log("POSTER File Name: " + req.filename)
console.log("book created")
console.log("File Name: " + req.file.originalname)
// console.log(newBook)
} catch (err) {
return res.send(err.message);
}
return res.redirect('library')
})
// UPDATE BOOK
router.post('/update-book/:id', upload.single('poster'), async function (req, res, next) {
const bookId = req.params.id;
try {
const updatedBook = { ...req.body };
if (req.file) {
updatedBook.poster = req.file.filename;
// console.log("Updated book poster ", updatedBook.poster)
const imagePath = path.join(__dirname, `../public/images/${req.body.oldimage}`)
// console.log(req.body.oldPosterName);
console.log(req.body.oldimage);
fs.unlinkSync(imagePath)
}
await BookCollection.findByIdAndUpdate(bookId, req.body);
await BookCollection.findByIdAndUpdate(bookId, updatedBook);
} catch (err) {
return res.render(err.message);
}
return res.redirect(`/details/${bookId}`)
})
// ๐ -------- DELETE OPERATION -------- ๐
router.get('/delete/:id', async function (req, res, next) {
const bookId = req.params.id;
try {
// const specificBook = await BookCollection.deleteOne({_id: bookId})
const specificBook = await BookCollection.findByIdAndDelete(bookId);
const posterPath = path.join(__dirname, `../public/images/${specificBook.poster}`)
if (fs.existsSync(posterPath)) {
// console.log(posterPath);
fs.unlinkSync(posterPath);
} else {
console.error("Book Data Deleted, but Poster Image File NOT FOUND !");
}
} catch (err) {
return res.render(err.message);
}
return res.redirect('/library');
})
4. nodemailer - to send mail
๐ ./utils/sendMail.js
// Import the nodemailer module for sending emails
const nodemailer = require("nodemailer");
// Export the sendMail function
exports.sendMail = (req, res) => {
// Create a transporter object using SMTP transport
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com", // SMTP server host
port: 465, // SMTP server port for secure connection
secure: true, // Use SSL/TLS for the connection
auth: {
user: `${process.env.SENDER_EMAIL}`, // Email address from environment variables
pass: `${process.env.NODEMAILER_PASSWORD}`, // Email password from environment variables
},
});
// Define email options
const mailOptions = {
from: `"Ayush Official ๐ป" <${process.env.SENDER_EMAIL}>`, // Sender address with a display name
to: req.body.email, // Recipient email address from the request body
subject: "NEWSLETTER SUBSCRIPTION โ", // Subject line of the email
// text: "Hello world?", // Plain text body (commented out)
html: `<h1>Welcome to Bookstore.</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum, earum.</p>
<button>Explore More</button>`, // HTML body of the email
};
// Send the email using the transporter object
transporter.sendMail(mailOptions, (err, info) => {
if (err) return res.send(err); // If an error occurs, send the error as a response
console.log(info); // Log the email info for debugging
return res.send(
"<h1 style='text-align:center;color: tomato; margin-top:10%'><span style='font-size:60px;'>โ๏ธ</span> <br />Email Sent! Check your inbox, <br/>check spam in case not found in inbox.</h1><a href='/'>HomePage</a>"
); // Send a success message as a response
});
};
๐./routes/index.route.js
const {sendMail} = require('../utils/sendMail');
// ๐ -------- HANDLE NODEMAILER -------- ๐
router.post('/send-mail', function (req, res, next) {
sendMail(req, res);
})
๐ ./views/contact.ejs
<form action="/send-mail" method="post" class="m-5 w-75 d-flex flex-column justify-content-around h-25">
<input type="email" class="fs-5 p-2 px-4 w-100" name="email" placeholder="Enter your email">
<button type="submit" class="btn btn-danger w-100 fw-bold my-5 fs-4">Subscribe </button>
</form>
ย