01 config: db connection, multer, nodemailer

ยท

7 min read

01 config: db connection, multer, nodemailer

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>
ย