Building a Pinterest-Inspired Image Gallery with Node.js, Express, EJS, MongoDB and Passport
Table of contents
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.
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
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
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;
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! 🚀
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!