Building a Pinterest-Inspired Image Gallery with Node.js, Express, EJS, MongoDB and Passport

Hello, Tech Enthusiasts! 👋

Welcome back to TechTonic Bytes, your go-to source for all things web development and technology. Today, I’m excited to share a project that combines some essential web development technologies to create a Pinterest-inspired image gallery. This project showcases how to use Node.js, Express, EJS, and Passport for authentication to build a full-stack web application.

Project Overview

In this project, we’ll develop a dynamic image gallery that mimics the layout and functionality of Pinterest. The gallery will feature:

  • User authentication with Passport Middleware

  • Uploading and displaying images created by a specific

  • A clean and user-friendly interface using EJS templates

  • A dedicated image page for downloading images created bt user

Tech Stack

For this project, we’ll be using the following technologies:

  • Node.js for the server-side runtime environment

  • Express for the web application framework

  • EJS for server-side templating

  • Passport Middleware for user authentication

  • MongoDB as the database

Getting Started

Before diving into the code, make sure you have Node.js and npm installed on your machine. If you haven’t already, you can download Node.js from here.

  1. Setting Up the Project

    Let’s start by setting up a new Node.js project. Open your terminal and run:

     npm i express-generator -g
     express filename --view=ejs
     cd filename
     npm init
    
  2. Install Necessary Packages

    First open the project in VS Code.

    Next, we’ll install the required packages for our project:

     npm install express ejs mongoose passport passport-local express-session connect-flash multer nodemon
    
  3. Setting Up the Server

    Create a file named app.js in your project root and set up a basic Express server:

    ```javascript var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var expressSession = require('express-session'); var passport = require("passport"); var flash = require("connect-flash"); const fs = require('fs');

var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users');

var app = express();

// view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs');

// Flash Messaging setup. app.use(flash());

// Express Session Setup app.use(expressSession({ resave: false, saveUninitialized : false, secret : "Shree" }))

// Passport Setup for Authentication and Authorization. app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser(usersRouter.serializeUser()); passport. deserializeUser(usersRouter.deserializeUser());

app.use(express.static(path.join(__dirname, 'public')));

app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter); app.use('/users', usersRouter);

// catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); });

// 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'); });

module.exports = app;


4. **Creating Models**

    In the models folder, create a User.js file for the user schema:

    ```javascript
    const mongoose = require('mongoose');
    const plm = require('passport-local-mongoose');

    mongoose.connect("mongodb://127.0.0.1:27017/Pinterest");

    const userSchema = new mongoose.Schema({
      username: {
        type: String,
        required: true,
        unique: true
      },
      password: {
        type: String,
      },
      name: {
        type: String,
        required: true
      },
      posts: [{
        type : mongoose.Schema.Types.ObjectId,
        ref : 'Post'
      }],
      profileImg: {
        type: String 
      },
      email: {
        type: String,
        required: true,
        unique: true
      },
      bio:{
        type:String,
      },
      website :{
        type: String,
      },
      instagram :{
        type: String,
      },
      facebook :{
        type: String,
      },
      linkedin :{
        type: String,
      },
    });

    userSchema.plugin(plm);

    const User = mongoose.model('User', userSchema);

    module.exports = User;

In the models folder, create another post.js file for the post schema:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  description:{
    type: String,
  },
  image : {
    type :String
  },
  likes: {
    type: Array,
    default: []
  },
  user : {
    type : mongoose.Schema.Types.ObjectId,
    ref : 'User'
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

const Post = mongoose.model('Post', postSchema);
module.exports = Post;
  1. Setting Up Routes

    In the routes folder, create index.js file for handling routes:

    index.js

     var express = require("express");
     var router = express.Router();
     const userModel = require("./users");
     const postModel = require("./posts");
     const passport = require("passport");
     const localStrategy = require("passport-local");
     const upload = require("./multer");
     const path = require('path');
     const fs = require('fs');
    
     passport.use(new localStrategy(userModel.authenticate()));
    
     // API Endpoint for SignUp Screen
     router.get("/", function (req, res, next) {
       res.render("index", { error: req.flash("error") });
     });
    
     // API Endpoint for Login Screen
     router.get("/login", function (req, res, next) {
       res.render("login", { error: req.flash("error") });
     });
    
     // API Endpoint for Profile Page
     router.get("/profile", isLoggedin, async function (req, res, next) {
       let user = await userModel
         .findOne({
           username: req.session.passport.user,
         })
         .populate("posts");
       res.render("profile", { user });
     });
    
     // API Endpoint for uploading Profile Pic
     router.post(
       "/uploadpic",
       isLoggedin,
       upload.single("profImg"),
       async (req, res, next) => {
         const user = await userModel.findOne({
           username: req.session.passport.user,
         });
         user.profileImg = req.file.filename;
         await user.save();
         res.redirect("/profile");
       }
     );
    
     // API Endpoint for Editing users Screen.
     router.get('/edit', isLoggedin, async function(req,res){
       let user = await userModel.findOne({
         username: req.session.passport.user,
       });
       res.render('edit', { user });
       console.log({user});
     })
    
     // API Endpoint for Editing Users.
     router.post('/update/:id', async function(req,res){
         const { name, username, bio, website, instagram, facebook, linkedin  } = req.body;
         await userModel.findByIdAndUpdate(req.params.id, { name, username, bio, instagram, facebook, linkedin, website  });
         res.redirect('/profile'); 
     })
    
     // API Endpoint for Adding Post
     router.get("/add", isLoggedin, async function (req, res, next) {
       let user = await userModel.findOne({
         username: req.session.passport.user,
       });
       res.render("add", { user });
     });
    
     // API Endpoint for uploading Users Post(images)
     router.post(
       "/createpost",
       isLoggedin,
       upload.single("postImg"),
       async (req, res) => {
         const user = await userModel.findOne({
           username: req.session.passport.user,
         });
         const postData = await postModel.create({
           title: req.body.title,
           description: req.body.description,
           image: req.file.filename,
           user: user._id,
         });
         user.posts.push(postData._id);
         await user.save();
         res.redirect("/profile");
       }
     );
    
     // API Endpoint for showing all feed.
     router.get("/feed", isLoggedin, async (req, res) => {
       const posts = await postModel.find().populate("user");
       console.log(posts);
       res.render("feed", { posts });
     });
    
     // API Endpoint for showing specific Post.
     router.get("/post/:postId", isLoggedin, getPostAndRender, async function (req, res) {
       const { postId } = req.params;
       const user = await userModel.findOne({
         username: req.session.passport.user,
       });
       const post = await postModel.findOne({ _id: postId, user: user._id });
    
       res.render("post", { post, user });
     }
     );
    
     // API Endpoint for Downloading users post
     router.get('/download/:filename', (req, res) => {
       const { filename } = req.params; // Extract filename from route parameters
       const filePath = path.join(__dirname, '../public/images/uploads', filename); // Construct full file path
    
       // Check if the file exists
       fs.access(filePath, fs.constants.F_OK, (err) => {
         if (err) {
           console.error('File does not exist:', filePath); // Log error for debugging
           return res.status(404).send('File not found'); // Respond with 404 if file does not exist
         }
    
         // Proceed with file download
         res.download(filePath, filename, (err) => {
           if (err) {
             console.error('File download error:', err); // Log error for debugging
             res.status(500).send('Error downloading file'); // Respond with 500 if there is an error during download
           }
         });
       });
     });
    
     // API Endpoint for SignUp
     router.post("/signup", async function (req, res, next) {
       let userData = new userModel({
         username: req.body.username,
         name: req.body.name,
         email: req.body.email,
       });
       userModel.register(userData, req.body.password).then(function () {
         passport.authenticate("local")(req, res, function () {
           res.redirect("/profile");
         });
       });
     });
    
     // API Endpoint for Login
     router.post(
       "/login",
       passport.authenticate("local", {
         successRedirect: "/profile",
         failureRedirect: "/login",
         failureFlash: true,
       }),
       function (req, res) { }
     );
    
     // API Endpoint for Logout
     router.get("/logout", function (req, res, next) {
       req.logout(function (err) {
         if (err) {
           return next(err);
         }
         res.redirect("/login");
       });
     });
    
     // Function for checking users logged in status
     function isLoggedin(req, res, next) {
       if (req.isAuthenticated()) return next();
       res.redirect("/login");
     }
    
     // Function for retriving only the Date and Time from createdAt field in Post Model.
     async function getPostAndRender(req, res) {
       const post = await postModel.findById(req.params.postId).exec();
       const user = await userModel.findOne({ username: req.session.passport.user });
    
       // Extract and format the date
       const date = post.createdAt;
       const formattedDate = `${date.getFullYear()}-${String(
         date.getMonth() + 1
       ).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
       const formattedTime = `${String(date.getHours()).padStart(2, "0")}:${String(
         date.getMinutes()
       ).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}`;
    
       res.render("post", { post, formattedDate, formattedTime, user });
     }
    
     module.exports = router;
    

    This was the basic setup for the Project.I In this project I have also used Multer middleware for storing users images, flash for messaging and local-strategy for authentication. For detailed code visit my Github Repo .

    Github Repo Link :- https://github.com/Shreeyog-Gaikwad/Pinterest-Clone.git

Conclusion

Building a Pinterest-inspired image gallery using Node.js, Express, EJS, and Passport is a fantastic way to enhance your full-stack web development skills. Through this project, we've covered key aspects of creating a dynamic and responsive web application, including setting up a server with Express, implementing user authentication with Passport, and designing a clean interface with EJS templates.

By following along with this tutorial, you should now have a solid understanding of how to:

  • Set up a Node.js and Express server

  • Implement user authentication with Passport

  • Use EJS for server-side templating

This project is not only a great learning experience but also a valuable addition to your portfolio. I encourage you to customize and expand on this foundation to create your own unique features and improvements.

Thank you for reading, and stay tuned to TechTonic Bytes for more exciting web development tutorials and projects. If you have any questions or feedback, feel free to leave a comment below. Until next time, keep building and exploring! 🚀

0
Subscribe to my newsletter

Read articles from Shreeyog Gaikwad directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shreeyog Gaikwad
Shreeyog Gaikwad

Passionate web developer with expertise in React, JavaScript and Node.js. Always eager to learn and share knowledge about modern web technologies. Join me on my journey of coding adventures and innovative web solutions!