Building a Pinterest Clone with Node.js, Express, and MongoDB
Table of contents
- Step 1: Setting Up the Project
- Step 2: Installing Passport and Other Dependencies
- Step 3: Configuring Express-Session and Passport in App.js
- Step 4: Creating User and Post Models
- Step 5:Creating Index.js.
- Step 6: Creating Registration, Login, Profile, Feed ,Upload and Logout Pages.
- Step 7: Setting Up Image Uploads with Multer
- Thank You !!
Introduction: Welcome to this step-by-step guide where we'll walk through the process of creating a Pinterest clone using Node.js, Express, and MongoDB. We'll be using popular packages such as Express-generator, Passport, Passport-local, Passport-local-mongoose, Mongoose, Express-session, and Multer. Let's get started!
Step 1: Setting Up the Project
Install Express Generator:
npm install -g express-generator
Create a New Express App:
express pinterest-clone --view=ejs
cd pinterest-clone
npm install
Step 2: Installing Passport and Other Dependencies
Install Passport:
npm install passport
Install Passport-local:
npm install passport-local
Install Passport-local-mongoose:
npm install passport-local-mongoose
Install Mongoose:
npm install mongoose
Install Express-Session:
npm install express-session
Step 3: Configuring Express-Session and Passport in App.js
var expressSession = require('express-session');
var passport = require('passport');
app.use(expressSession({
resave:false,
saveUninitialized:false,
secret: 'your-secret-key'
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(usersRouter.serializeUser());
passport.deserializeUser(usersRouter.deserializeUser());
Step 4: Creating User and Post Models
Create models/user.js
:
var mongoose = require('mongoose');
var plm = require('passport-local-mongoose');
mongoose.connect("mongodb://127.0.0.1:27017/pnterestServer");
const userschema = mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
fullname: {
type: String,
required: true,
},
password: {
type: String,
},
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref:'Post'
}],
dp: {
type: String,
}
});
userschema.plugin(plm);
module.exports = mongoose.model('user', userschema);
Create models/post.js
:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
posttext :{
type: String,
required: true
},
image:{
type : String,
},
user:{
type:mongoose.Schema.Types.ObjectId,
ref:'User'
},
createdate:{
type: Date,
default: Date.now,
},
likes:{
type: Array,
default: [],
}
})
module.exports = mongoose.model('Post', postSchema);
Step 5:Creating Index.js.
configuring express, router, passport-local, passport.
Create index.js
:
var express = require('express');
var router = express.Router();
var userModel = require('./users');
var postModel = require('./post');
const localStrategy = require('passport-local');
const upload = require('./multer');
const passport = require('passport');
passport.use(new localStrategy(userModel.authenticate()));
router.get('/', function(req, res, next) {
res.render('index');
});
router.get('/profile', isLoggedin,async function(req, res, next) {
const user = await userModel.findOne({
username : req.session.passport.user
})
.populate('posts')
console.log(user);
res.render('profile',{user});
});
router.get('/login', function(req, res, next) {
res.render('login',{ error: req.flash('error')} );
});
router.get('/feed', function(req, res, next) {
res.render('feed');
});
router.post('/register', function(req, res) {
const{username , email , fullname } = req.body;
const userdata = new userModel({username, email,fullname});
console.log("done");
userModel.register(userdata,req.body.password)
.then(function() {
passport.authenticate("local")(req, res,function(){
res.redirect('/profile');
})
})
});
router.post('/login',passport.authenticate("local",{
successRedirect:"/profile",
failureRedirect:"/login",
failureFlash:true
}) ,function(res,req){});
router.get('/logout', function(req, res, next){
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/login');
});
});
router.post('/upload', isLoggedin,upload.single('file') , async function(req, res, next){
if(!req.file){
return res.status(404).send("No files were uploaded")
}
const user = await userModel.findOne({username:req.session.passport.user})
const post = await postModel.create({
image:req.file.filename,
posttext:req.body.filecaption,
user:user._id
})
user.posts.push(post._id)
await user.save()
res.redirect("/profile");
});
function isLoggedin(req,res,next){
if(req.isAuthenticated()){
return next();}
res.redirect('/login');
}
module.exports = router;
Step 6: Creating Registration, Login, Profile, Feed ,Upload and Logout Pages.
Create views/register.ejs
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<div class="container">
<div class="content">
<!--pinterest logo-->
<img src="https://i.pinimg.com/originals/d3/d1/75/d3d175e560ae133f1ed5cd4ec173751a.png" alt="pin logo" class="img1" />
<p class="header">Register to see more</p>
<form action="/register" method="post">
<input name="email" type="email" placeholder="Email" class="detail" /><br />
<input name="fullname" type="text" placeholder="fullname" class="detail" /><br />
<input name="username" type="text" placeholder="username" class="detail" /><br />
<input name="password" type="password" placeholder="Password" class="detail" />
<input type="submit" class="btn int" value="Register">
</form>
<p class="or">OR</p>
<button class="btn fbk">
<i
class="fab fa-facebook fa-lg"
style="color: white; padding-right: 10px"
></i
><a href="#">Continue with Facebook</a></button
><br />
<button class="btn ggl">
<i
class="fab fa-google"
style="color: rgb(11, 241, 22); padding-right: 10px"
></i
><a href="#">Continue with Google</a>
</button>
<footer>
<p>
By continuing, you agree to Pinterest's
<b>Terms of Service, Privacy policy.</b>
</p>
<hr />
</footer>
</div>
</div>
<a href="/login" class="upbtn">Log in</a>
</body>
</html>
Create views/login.ejs
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<div class="container">
<div class="content">
<!--pinterest logo-->
<img src="https://i.pinimg.com/originals/d3/d1/75/d3d175e560ae133f1ed5cd4ec173751a.png" alt="pin logo" class="img1" />
<p class="header">Log in to Your Account</p>
<% if(error.length > 0){ %>
<p class="error"> <%= error %> </p>
<% } %>
<form action="/login" method="post">
<input name="username" type="text" placeholder="username" class="detail" /><br />
<input name="password" type="password" placeholder="Password" class="detail" />
<input type="submit" class="btn int" value="Login">
</form>
<a href="/">Forgot your password?</a>
<p class="or">OR</p>
<button class="btn fbk">
<i
class="fab fa-facebook fa-lg"
style="color: white; padding-right: 10px"
></i
><a href="#">Continue with Facebook</a></button
><br />
<button class="btn ggl">
<i
class="fab fa-google"
style="color: rgb(11, 241, 22); padding-right: 10px"
></i
><a href="#">Continue with Google</a>
</button>
<footer>
<p>
By continuing, you agree to Pinterest's
<b>Terms of Service, Privacy policy.</b>
</p>
<hr />
<p>Not on Pinterest yet?</p> <a href="/">Sign up</a>
</footer>
</div>
</div>
<a href="/" class="upbtn">Sign up</a>
</body>
</html>
Create stylesheets/style.css
:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: gray;
font-family: Arial, Helvetica, sans-serif;
}
.container {
background-color:white;
width: 480px;
min-height: 650px;
position: absolute;
margin: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 10px;
box-shadow: 2px 2px 3px -1px rgba(0, 0, 0, 0.5);
}
.content {
width: 56%;
margin: auto;
text-align: center;
}
.img1{
width: 70px;
position: relative;
top: 20px;
}
.header {
font-size: 32px;
font-weight: 700;
margin-top: 40px;
margin-bottom: 10px;
line-height: 34px;
}
.detail {
width: 100%;
height: 48px;
margin: 5px;
padding: 15px;
font-size: 15px;
color: gray;
border: 2px solid rgb(218, 214, 214);
border-radius: 15px;
}
.detail:focus {
box-shadow: 0px -7px 3px -2px rgba(15, 91, 231, 0.4),
0px 7px 3px -2px rgba(15, 91, 231, 0.4),
7px 0px 3px -2px rgba(15, 91, 231, 0.4),
-7px 0px 3px -2px rgba(15, 91, 231, 0.4);
outline: none;
}
h4 {
font-size: 13px;
font-weight: 700;
position: relative;
left: -60px;
margin: 5px;
}
.btn {
width: 100%;
height: 40px;
border: hidden;
border-radius: 20px;
font-size: 18px;
margin: 5px 0px;
}
.int {
background-color: #f30d19;
margin: 10px 0px;
color: white;
font-size: 16px;
font-weight: 700;
cursor: pointer;
}
.int:hover{
background-color: #df0812;
}
.or {
font-size: 15px;
font-weight: 700;
margin: 10px 0px;
}
.fbk {
background-color: rgb(9, 128, 240);
margin: 5px 0px;
font-size: 16px;
font-weight: 700;
}
.fbk a {
text-decoration: none;
color: white;
text-align: center;
letter-spacing: 1px;
}
.fbk:hover{
background-color: rgb(8, 110, 206);
}
.ggl{
text-align: center;
background-color: #ccc;
}
.ggl a {
text-decoration: none;
color: black;
font-weight: 600;
font-size: 16px;
letter-spacing: 1px;
}
.ggl:hover{
background-color: #a0a0a0;
}
footer p {
font-size: 12px;
margin: 10px;
opacity: 0.7;
}
p:last-child {
opacity: 1;
}
hr {
width: 50%;
opacity: 0.4;
margin-left: 25%;
}
.upbtn{
position: relative;
text-decoration: none;
color: black;
top: 50px;
left: 140px;
font-size: 16px;
font-weight: 700;
text-align: center;
width: 126px;
padding: 11px 20px;
background-color: rgb(241, 236, 236);
border-radius: 20px;
}
.error{
font-weight: 500;
color: red;
margin: 7px 0;
text-transform: capitalize;
}
Create views/profile.ejs
:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Profile</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="/stylesheets/profile.css">
</head>
<body>
<div class="containerrr">
<div class="carddd">
<div class="profile-picture">
<img src="https://images.unsplash.com/photo-1488161628813-04466f872be2?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTB8fG1vZGVsfGVufDB8fDB8fHww" alt="Profile Picture">
</div>
<h2 class="name"><%= user.fullname %></h2>
<h3 class="username">@<%= user.username %></h3>
<p class="tagline">bio</p>
<p class="description">some description</p>
<a href="/logout" class="button">Log out</a> <hr>
<form action="/upload" method="post" enctype="multipart/form-data">
<input class="caption" type="text" name="filecaption" placeholder="Caption">
<input type="file" name="file" >
<input class="upload" type="submit" value="Upload">
</form>
</div>
</div>
<div class="cards flex"> >
<% user.posts.forEach(function(post){ %>
<div class="container">
<div class="card" style="width: 18rem;">
<img src="/images/uploads/<%= post.image %> " class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"> <%= post.posttext %> </h5>
</div>
</div>
</div>
<% }) %>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>
Create stylesheets/profile.css
:
body {
font-family: Arial, sans-serif;
background-color: #dadada;
margin: 0;
padding: 0;
}
.containerrr {
max-width: 80%;
margin: 0 auto;
padding: 20px;
}
.flex{
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: start;
}
.carddd {
background-color: #fff;
border-radius: 5px;
padding: 20px;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.profile-picture {
width: 150px;
height: 150px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto 20px;
}
.profile-picture img {
width: 100%;
height: 100%;
object-fit: cover;
}
.name {
font-size: 24px;
margin: 0;
}
.username {
font-size: 18px;
color: #777;
margin: 10px 0;
}
.tagline,
.description {
color: #555;
margin: 10px 0;
}
.button {
display: inline-block;
padding: 10px 20px;
background-color: #f30d19;
color: #fff;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s;
}
.button:hover {
background-color: #d80e18;
}
.caption{
border-radius: 30px;
border: 1px solid black;
padding: 7px 11px;
}
.upload{
display: inline-block;
padding: 10px 20px;
background-color: #f30d19;
color: #fff;
border: none;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s;
}
Create views/logout.ejs
:
Create views/feed.ejs
:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
<title>Pinteresting | Gallery Layout</title>
<link rel="stylesheet" href="/stylesheets/feed.css">
</head>
<!-- A Simple non functional Pinterest inspired page layout created using only HTML and CSS.
Created by M. Hassler - Hassified -->
<!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script> -->
<body>
<div class="container">
<div class="column">
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/1408221/pexels-photo-1408221.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Pink flowers</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/736230/pexels-photo-736230.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/2350377/pexels-photo-2350377.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
</div>
<div class="column">
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/3601531/pexels-photo-3601531.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Pink flowers</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/772520/pexels-photo-772520.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/3262437/pexels-photo-3262437.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
</div>
<div class="column">
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/1697912/pexels-photo-1697912.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Pink flowers</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/1591140/pexels-photo-1591140.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/2057047/pexels-photo-2057047.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
</div>
<div class="column">
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/60909/rose-yellow-flower-petals-60909.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/4121204/pexels-photo-4121204.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/4122767/pexels-photo-4122767.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
<div class="content-wrapper">
<div class="image-wrapper">
<div class="overlay">
<div>
<p>Creator</p>
<button class="save">Save</button>
</div>
<div>
<button class="spheric-button">
twitter.com
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/3580/3580382.png" alt="">
</button>
<button class="round-button">
<img src="https://cdn-icons-png.flaticon.com/512/512/512142.png" alt="">
</button>
</div>
</div>
<img src="https://images.pexels.com/photos/2626152/pexels-photo-2626152.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="">
</div>
<p>Cool image</p>
</div>
</div>
</div>
</body>
</html>
Create stylesheets/feed.css
:
* {
padding: 0;
margin: 0;
box-sizing: border-box;
--red: #e60023;
}
body, html {
width: 100%;
}
body {
display: grid;
place-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", メイリオ, Meiryo, "MS Pゴシック", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";;
}
.container {
display: grid;
grid-template-columns: repeat(4, 1fr);
max-width: 100%;
column-gap: 1em;
padding: 2em;
}
.image-wrapper {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
.image-wrapper > img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 16px;
}
.column {
display: flex;
flex-direction: column;
gap: 3em;
}
.content-wrapper p {
padding: 0.5em 0 0 0.5em;
font-weight: bold;
}
.overlay {
cursor: zoom-in;
position: absolute;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
border-radius: 16px;
padding: 0.75em;
color: white;
opacity: 0;
}
.overlay:hover {
opacity: 1;
}
.overlay div {
display: flex;
width: 100%;
justify-content: space-between;
}
.overlay div > p {
cursor: pointer;
}
.overlay div .round-button:nth-child(2) {
margin-left: auto;
margin-right: 0.7em;
}
.round-button {
width: 2.4em;
height: 2.4em;
border: none;
border-radius: 50%;
display: grid;
place-items: center;
opacity: 0.8;
}
.round-button img {
width: 60%;
height: 60%;
}
.spheric-button {
border-radius: 50px;
border: none;
padding: 0.5em 01.25em;
opacity: 0.8;
font-weight: 800;
}
.spheric-button:hover, .round-button:hover {
opacity: 1;
cursor: pointer;
}
.save {
padding: 1em 1.5em;
border: none;
border-radius: 50px;
color: white;
font-weight: 900;
cursor: pointer;
background-color: var(--red);
}
.save:hover {
background-color: #ad081b;
}
@media (max-width: 1050px) {
.container {
grid-template-columns: repeat(3, 1fr);
}
.column:last-child {
display: none;
}
}
@media (max-width: 800px) {
.container {
grid-template-columns: repeat(2, 1fr);
}
.column:nth-child(3) {
display: none;
}
}
@media (max-width: 550px) {
.container {
grid-template-columns: 1fr;
gap: 3em;
}
.column:nth-child(3) {
display: flex;
}
.column:last-child {
display: flex;
}
}
Step 7: Setting Up Image Uploads with Multer
Install Multer:
npm install uuid multer
Create multer.js
:
const multer = require('multer');
const {v4 : uuidv4} = require('uuid');
const path = require('path');
const storage = multer.diskStorage({
destination : function(req,file ,cb){
cb(null,'./public/images/uploads')
},
filename: function(req,file,cb){
const uniqueFilename = uuidv4();
cb(null , uniqueFilename+path.extname(file.originalname));
}
});
const upload = multer({storage : storage});
module.exports = upload;
index.js
const upload = require('./multer');
router.post('/upload', isLoggedin,upload.single('file') , async function(req, res, next){
if(!req.file){
return res.status(404).send("No files were uploaded")
}
const user = await userModel.findOne({username:req.session.passport.user})
const post = aw/ait postModel.create({
imag/////e:req.file.filename,
posttext:req.body.filecaption,
user:user._id
})
user.posts.push(post._id)
await user.save()
res.redirect("/profile");
});
Thus we have successfully created a Pinterest clone using Node.js, Express, and MongoDB. This is a basic implementation, and you can further enhance it by adding features such as image categorization, user profiles, and more.
Thank You !!
Subscribe to my newsletter
Read articles from Aditya Pippal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Aditya Pippal
Aditya Pippal
I'm Aditya Pippal, a Pre-final year Electronics and Communication Engineering student at UIET, Punjab University. I'm a passionate web developer on a mission to master the intricacies of full-stack development. With a keen interest in technologies like React, Node.js, and MongoDB, I find joy in crafting seamless digital experiences. My journey is fueled by a love for problem-solving and a commitment to staying on the cutting edge of web development. From delving into server-side scripting to perfecting user interfaces, I believe in the power of clean code and continuous learning. Join me in this exciting adventure of building, innovating, and making a positive impact in the ever-evolving world of web development! 🚀🌐 #WebDeveloper #CodePassion #TechEnthusiast #FullStackDev