Landing Page builder Backend (part 2)

Landing Page builder Backend (part 2)

In part 1 we created four forms and got the data into state of one parent component, Now we will create backend api's and send data to the backend as well as map the user's data into one react component which will be his portfolio. Let's Start

File Structure

Backend
-src
--db
---mongoose.js
--middlware
---upload.js
--models
---user.js
--routers
---user-routers.js
--uploads
-app.js

mongoose.js (connect nodejs with mongodb atlas)

require('dotenv').config();
const mongoose = require('mongoose');
// Connecting mongodb database
function connectDB() {
  mongoose.connect(process.env.PORTFOLIO_DBURL, { useNewUrlParser: true, useUnifiedTopology: true });
  const connection = mongoose.connection;
  return new Promise((resolve, reject) => {
    connection.once('open', () => {
      console.log('Database connected');
      resolve();
    });
  }).catch(err => {
    console.log('Connection failed ');
    reject(err);
  });
}
module.exports = connectDB;

user.js

There are two database schemas connected through unique _ids in mongodb

  1. fileSchema (for user image)

  2. userSchema (for other information)

const mongoose = require("mongoose");
const multer = require("multer");
const fileSchema = new mongoose.Schema({
  // here we setup the file name so how our file going to store
   data: Buffer,
   contentType: String,
  user:{ type: mongoose.Schema.Types.ObjectId,
    ref: 'User'}
});
const userSchema = new mongoose.Schema({
  name: String,
  email: String,
  role: String,
  description:String,
  linkedinlink: String,
  githublink: String,
  behancelink:String,
  skill: [String],
  projects: [
    {
      projectName: String,
      projectDescription: String,
      projectLink: String,
    },
  ],
});
const File = mongoose.model("File", fileSchema);;
const User = mongoose.model("User", userSchema);
module.exports = {User,File};

upload.js (middleware to assign storage for images using multer)

const multer = require("multer");
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "./src/uploads/");
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  },
});
const upload = multer({ storage:storage, limits: { fileSize: 1000000 * 20 } });
module.exports = upload;

user-routers.js

You can observe in /user/create API we have passed our image (myFile) in a upload middleware function and other data inside the function so that first new user gets created then the file will be uploaded in file schema with the same user id so that it can work as a foreign key to connect both schemas.

const express = require("express");
const fs = require("fs");
const nodemailer = require("nodemailer"); 
const router = new express.Router();
const upload = require("../middleware/upload.js");
const MongoClient = require("mongodb").MongoClient;
const {User,File} = require("../models/user.js");
require("dotenv").config();
const client = new MongoClient(process.env.PORTFOLIO_DBURL);
client.connect();
// creating smtpProtocol to send mail for verification
const smtpProtocol = nodemailer.createTransport({
  service: "gmail",
  auth: {
      user: "ishanp2022@gmail.com",
      pass: process.env.EMAIL_PASSWORD
  },
  tls: {
      rejectUnauthorized: false
  }
})
// This is the api which will insert all data and photo of user in database

router.post("/user/create", upload.single("myFile"),async (req, res) => {
  const {
    name,
    email,
    role,
    description,
    githublink,
    behancelink,
    linkedinlink,
    skill,
    projects,
  } = req.body;
  const user = new User({
    name,
    email,
    role,
    description,
    githublink,
    behancelink,
    linkedinlink,
    skill,
    projects,
  });
  try {
    await user.save();
    if (!req.file) {
     console.log("all fields are required")
    }
    else {
      const file = new File({
        data: fs.readFileSync("./src/uploads/" + req.file.filename),
        contentType: "image/png",
        user: user._id,
      });
      const response = await file.save();
      res.status(201).json({message: "new user with image created" });
    }
  } catch (e) {
    res.send("can't add user");
  }
});
// This api is used to load user portfolio
router.get("/user/data", async (req,res)=>{
  const {urlMail} = req.query;
  const user = await User.findOne({email:urlMail});
  const userId = user._id;
  const file = await File.findOne({user:userId});
  res.status(201).json([{user,file}]);
});
// This api will verify email by sending otp
router.post("/email/validate",async (req,res)=>{
  const {email} = req.query;
  const {otp}= req.query;
  const findEmail = await User.findOne({email:email});
  if(!findEmail){
    var mailOptions = {
        from: "ishanp2022@gmail.com",
        to: email,
        subject: "OTP for verification",
        html:`Your otp for verification is : ${otp}`
    }
    smtpProtocol.sendMail(mailOptions, function (err, success) {
      if (err) {
          console.log(err);
      }
      else {
        res.send("user not found");
      }
      smtpProtocol.close();
  });
  }
  else{
    res.send("User found");
  }
});
// Api to check whether user is already present or not.
router.post("/find/user", async (req,res)=>{
  const {email} = req.query;
  const findEmail = await User.findOne({email:email})
  if(!findEmail){
    res.send("User not found");
  }
  else{
    res.send("User found");
  }
})
module.exports = router;

App.js

const express = require("express");
const cors = require("cors")
const path = require("path");
const bodyParser = require("body-parser");
const userRouter = require("./routers/user-routers.js");
const app = express();
app.use(cors({
  origin:["http://localhost:3000", "https://designfolio.onrender.com"],
}));
app.use(express.json());
app.use(
  express.urlencoded({
    extended: false,
  })
);
const connectDB = require("./db/mongoose.js");
connectDB();
app.use(userRouter)
const port = process.env.PORT || 8000
app.listen(port, () => console.log(`Server is running on ${port}`))

Once the user gets created we can assign him route to his portfolio which will be sitename/mailidbefore@ (e.g sitename/249patelishan if my mail Id is )

Data base example

Userprofile.jsx

Using base64String to decrypt the image data and map into an img tag which is present in binary values in the database.

 import React, { useState, useEffect } from "react";
import axios from "axios";
import Loading from "../Loadingspinner/Loading";
import "./Userprofile.css";
import mailImg from "../../images/gmail.png";
import linkedinImg from "../../images/linkedin.png";
import githubImg from "../../images/github-sign.png";
import behanceImg from "../../images/behance.png";
import { useParams } from "react-router-dom";
import { motion } from "framer-motion";
import {Helmet} from "react-helmet";
function UserProfile() {
  const [fileData, setFileData] = useState([]);
  const [userData, setUserData] = useState([]);
  const [loading, setLoading] = useState(true);
  let { urlMail } = useParams();
  useEffect(() => {
    axios({
      method: "GET",
      url: `${process.env.REACT_APP_PORTFOLIO}/user/data?urlMail=${urlMail}@gmail.com`,
    })
      .then((res) => { 
        // After getting response we will store it in state and then map into html tags to make it look beautiful.
        setFileData([res.data[0].file]);
        setUserData([res.data[0].user]);
        setLoading(false);
      })
      .catch((err) => {
        console.log(err);
      });
  }, [urlMail]);

  return (
    <div style={{ backgroundColor: "white" }} className="flex justify-center">
      {/* <Helmet>
        <title>{userData[0].name}</title>
      </Helmet> */}
      {loading ? (
        <Loading />
      ) : (
        <motion.div
        initial={{opacity:0}}
        animate={{opacity:1}}
        transition={{delay:0.5}}
        style={{ width: "920px" }}>
          {/* user image and user description */}
          <div className="firstBox">
            <div className="userImgBox">
              {fileData.map((singleData) => {
                const base64String = btoa(
                  new Uint8Array(singleData.data.data).reduce((data, byte) => {
                    return data + String.fromCharCode(byte);
                  }, "")
                );
                return (
                  <img
                    src={`data:${singleData.contentType};base64,${base64String}`}
                    alt="trying to do it"
                    className="userImg"
                  />
                );
              })}
            </div>
            <div className="userInfo items-center">
              {userData.length !== 0 ? (
                <div>
                  {" "}
                  <div className="font-poppins font-bold userName">
                    {userData[0].name}
                  </div>
                  <div className="font-poppins userRole">
                    {userData[0].role}
                  </div>
                  <div className="userDescription font-poppins">
                    {userData[0].description}
                  </div>
                </div>
              ) : null}
            </div>
          </div>
          {/* user skills */}
          <div>
            <p className="font-poppins text-center pt-4 font-bold text-2xl">
              Skills
            </p>
            {userData.length !== 0 ? (
              <div className="flex flex-wrap justify-center mt-2">
                {userData[0].skill.map((skills, index) => (
                  <div className="font-poppins skillsData" key={index}>
                    {skills}
                  </div>
                ))}
              </div>
            ) : null}
          </div>
          {/* user projects */}
          <div>
            <p className="font-poppins text-center projectHeading font-bold text-2xl">
              Projects
            </p>
            {userData.length !== 0 ? (
              <div className="flex flex-wrap justify-center mt-4">
                {userData[0].projects.map((project, index) => (
                  <div className="projectCard" key={index}>
                    <p className="projectTitle text-lg p-2 font-bold font-poppins">
                      {project.projectName}
                    </p>
                    <p className="projectDescription text-sm p-2 font-poppins">
                      {project.projectDescription}
                    </p>
                    <div className="projectBtn link-btn flex">
                      <a
                        href={project.projectLink}
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        Link
                      </a>
                      <div className="linkSvg">
                        <svg
                          width="16"
                          height="16"
                          viewBox="0 0 19 19"
                          fill="none"
                          xmlns="http://www.w3.org/2000/svg"
                        >
                          <path
                            d="M7.24563 3.73352V5.73298H2.23476V16.7301H13.2587V11.7314H15.263V17.7298C15.263 17.9949 15.1574 18.2492 14.9695 18.4367C14.7815 18.6242 14.5266 18.7295 14.2608 18.7295H1.23258C0.966789 18.7295 0.711882 18.6242 0.523938 18.4367C0.335994 18.2492 0.230408 17.9949 0.230408 17.7298V4.73325C0.230408 4.4681 0.335994 4.21382 0.523938 4.02633C0.711882 3.83885 0.966789 3.73352 1.23258 3.73352H7.24563ZM18.2695 0.734314V8.73219H16.2652V4.14641L8.45525 11.9383L7.03818 10.5247L14.8471 2.73378H10.2521V0.734314H18.2695Z"
                            fill="white"
                          />
                        </svg>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            ) : null}
          </div>
          {/* User Contact  */}
          <p className="font-poppins text-center projectHeading font-bold text-2xl">
            Socials
          </p>
          {userData.length !== 0 ? (
            <div className="flex justify-center contactDiv">
              <a
                className="mx-4"
                href={`mailto:${userData[0].email}`}
                rel="noreferrer"
                target="_blank"
              >
                <img className="contactImg" src={mailImg} alt="mailId" />
              </a>
              <a
                className="mx-4"
                href={userData[0].linkedinlink}
                rel="noreferrer"
                target="_blank"
              >
                <img className="contactImg" src={linkedinImg} alt="linkedin" />
              </a>
              {userData[0].githublink!==""?<a
                className="mx-4"
                href={userData[0].githublink}
                rel="noreferrer"
                target="_blank"
              >
                <img className="contactImg" src={githubImg} alt="github" />
              </a>:null}
              {userData[0].behancelink!==""?<a
                className="mx-4"
                href={userData[0].behancelink}
                rel="noreferrer"
                target="_blank"
              >
                <img className="contactImg" src={behanceImg} alt="github" />
              </a>:null}    
            </div>
          ) : null}
        </motion.div>
      )}
    </div>
  );
}
export default UserProfile;

Frontend App.js routes

 <BrowserRouter>
      <Routes>
      <Route exact path ="/" element={<Dashboard/>}/>
        <Route exact path ="/getstarted" element={<MailValidation/>}/>
        <Route path ="/details" element={<Portfolio/>}/>
        <Route path ="/blog" element={<Blog/>}/>
        <Route path ="/validate" element={<Validate/>}/>
        <Route path ="/usercreated" element={<Usercreated/>}/>
        <Route exact path ="/:urlMail" element={<UserProfile/>}/>
      </Routes>
      </BrowserRouter>

Wrapping up

That's it, everyone. Thank you so much for reading I hope you got some help or learned something new at the end of this two-part blog.

Github link: https://github.com/ishan249/design-folio-backend

Blog Part 1: https://hashnode.com/post/clf1blfg5000509jz7ar3bitx