How to Create a Google Calendar Like Application With Notification Service Integrated For Cross-User and 3rd Party Collaboration?

Sanjeev Kumar
February 10, 2024
TABLE OF CONTENTS

In this article, we delve into the development process of a feature-rich calendar application akin to Google Calendar, augmented with robust notification services. Meet Suprsend-DoodleCalendar, a versatile tool designed to streamline event creation, sharing, and management, all while keeping users promptly informed which we would use as the demo applicattion. Leveraging a potent technology stack comprising React.js, Bootstrap, Node.js, and MongoDB, we'll guide you through the intricate web of frontend and backend intricacies. From user authentication to event manipulation and cross-user collaboration, every aspect of the application will be meticulously dissected.

Technology Stack

  • Frontend - React.js, Bootstrap
  • Backend - Node.js
  • Database - MongoDB
  • Notification SDK - SuprSend Node SDK & APIs

File structure:

Application Features:

  • Authentication (Login and Register)
  • 2 types of email notifications- event_created, event_shared, and event_ reminder
  • User can create multiple events in the same calendar file just like Google Calendar
Find deployed application: https://suprsend-doodlecal.netlify.app/
Github: SuprSend-NotificationAPI/Suprsend-DoodleCalender: Doodle Calender (github.com)

Application UI

Login Page

A screenshot of a login pageDescription automatically generated

Register Page

HomePage aka Calendar with day, week and year view

A screenshot of a calendarDescription automatically generated

The user can change the information by clicking on any date/ view, upon which a modal will open asking for necessary changes. User can add, edit, deleter or share the event. The sharing will work for users who are registered on the website similar to Google Calendar.

A screenshot of a meetingDescription automatically generated
A screenshot of a computerDescription automatically generated

Frontend Codes:

You can get the necessary front-end codes of the application from our Github. SuprSend-NotificationAPI/Suprsend-DoodleCalender: Doodle Calender

Modify the user interface design as per your choice. We'll look primarily into the backend and integrations for this tool.

Backend Codes

  
      // Middleware/fetchuser.js

      require("dotenv").config();
      var jwt = require("jsonwebtoken");
      const JWT_SECRET = process.env.JWT_SECRET;

      const fetchuser = (req, res, next) => {
          // Get the user from the JWT token and add ID to the request object
          const token = req.header("auth-token");
          if (!token) {
              res.status(401).send({ error: "Please authenticate using a valid token" });
          }
          try {
              const data = jwt.verify(token, JWT_SECRET);
              req.user = data.user;
              next();
          } catch (error) {
              res.status(401).send({ error: "Please authenticate using a valid token" });
          }
      };

      module.exports = fetchuser;

      // Models/user.js

      const mongoose = require('mongoose');
      const { Schema, model } = mongoose;

      const UserSchema = new Schema({
          email: { type: String },
          name: String,
          phone: { type: Number },
          password: String
      });

      const UserModel = model('User', UserSchema);

      module.exports = UserModel;

      // Models/event.js

      const mongoose = require("mongoose");
      const { Schema } = mongoose;

      const collaboratorSchema = new mongoose.Schema({
          user: {
              type: mongoose.Schema.Types.ObjectId,
              ref: 'user',
              required: true,
          },
      });

      const EventSchema = new Schema({
          author: {
              type: mongoose.Schema.Types.ObjectId,
              ref: "user",
          },
          id: {
              type: String,
          },
          title: {
              type: String,
          },
          start: {
              type: Date,
          },
          end: {
              type: Date,
          },
          allDay: {
              type: Boolean
          },
          collaborators: [collaboratorSchema],
      });

      module.exports = mongoose.model("Event", EventSchema);

      // Connection to database db.js

      require("dotenv").config();
      const mongoose = require("mongoose");

      const connectToMongo = async () => {
          const URI = process.env.MONGO_URI;
          mongoose.connect(URI, { useNewUrlParser: true });
      };

      module.exports = connectToMongo;
                             
        
    

Setting up the backend

  
      require("dotenv").config()
      const connectToMongo =require("./db");
      connectToMongo();
      const express = require("express")
      var fetchuser = require("./middleware/fetchUser")
      const app  = express();
      const port = 4000;
      const User = require("./models/user")
      const Events = require("./models/event")
      var jwt = require("jsonwebtoken")
      const JWT_SECRET = process.env.JWT_SECRET
      const cors = require('cors');
      const { Suprsend} = require("@suprsend/node-sdk");
      const { Event } = require("@suprsend/node-sdk");
      const supr_client = new Suprsend(process.env.WKEY, process.env.WSECRET);
      //middleware if we want to read the json and req file
      app.use(cors());
      app.use(express.json());
        
    

Configuring Routes

  
      // Register route

      /******** Add to database and register on Suprsend ********/

      app.post("/register", async (req, res) => {
          let success = false;
          try {
              const user = await User.create({
                  email: req.body.email,
                  name: req.body.name,
                  phone: req.body.countryCode + req.body.phone,
                  password: req.body.password
              });
              const data = {
                  user: {
                      id: user.id
                  }
              };
              const authtoken = jwt.sign(data, JWT_SECRET);
              success = true;
              const distinct_id = user.email;
              const user1 = supr_client.user.get_instance(distinct_id);
              user1.add_email(user.email);
              user1.add_sms("+" + user.phone);
              user1.add_whatsapp("+" + user.phone);
              const response = user1.save();
              response.then((res) => console.log("response", res));
              res.json({ success, authtoken });
          } catch (error) {
              console.error(error.message);
              res.status(500).send("Some error occurred");
          }
      });

      // Login route

      /************ Login user **********/

      app.post("/login", async (req, res) => {
          try {
              const { email, password } = req.body;
              let success = false;
              let user = await User.findOne({ email: email });
              if (!user) {
                  return res.status(400).json({ success, message: "User does not exist" });
              }
              if (password !== user.password) return res.status(400).json({ success, message: "Password is wrong" });
              const data = {
                  user: {
                      id: user.id
                  }
              };
              const authtoken = jwt.sign(data, JWT_SECRET);
              success = true;
              res.json({ success, authtoken });
          } catch (error) {
              console.error(error.message);
              res.status(500).send("Some error occurred");
          }
      });

      app.get("/getdata", (req, res) => {
          res.send("Hello");
      });
        
    

Fetching all events

  
      app.get("/fetchallevents",fetchuser,async(req,res)=>{
          try {
            const events = await Events.find({ collaborators: { $elemMatch: { user: req.user.id } } })
            .sort({ updatedAt: -1 });
            res.json(events);
          } catch (error) {
          console.error(error.message);
          res.status(500).send("some error occured");
          }
      })
        
    

Adding and deleting events

  
      // Add events :- 

      app.post("/addevent",fetchuser,async(req,res)=>{
          try {
            const event = new Events({
              author : req.user.id,
              id : req.body.id,
              title : req.body.title,
              start : req.body.start,
              end : req.body.end,
              allDay : req.body.allDay,
              collaborators: [{ user: req.user.id }],
          })
          const savedevent = await event.save();
          const updatedEvent = await Events.findOneAndUpdate(
            { _id: savedevent._id }, 
            { $set: { id: savedevent._id } },
            { new: true } 
          );
          res.json(savedevent);
          } catch (error) {
            console.error(error.message);
            res.status(500).send("some error occured");
          }
        })


      // Delete events

      app.delete("/deleteevent/:id",async (req, res) => {
        try {
            let event = await Events.findById(req.params.id);
            if(!event){return res.status(404).send("NOT Found")}
            event = await Events.findByIdAndDelete(req.params.id);
            res.json({event});
        } catch (error) {
            console.error(error.message);
            res.status(500).send("some error occured");
        }
      })
        
    

Sharing Events

  
      app.post("/shareevent", fetchuser, async (req, res) => {
        try {
          let success = false;
          const { share, eventid } = req.body;
          const user = await User.findOne({ email: share });
          if (!user) {
            return res.status(404).json({ success, message: "no such user exists" });
          }
          let user2 = await User.findById(req.user.id);
          let event1 = await Events.findById(eventid);
          if (!event1) {
            return res.status(404).json({ success,error: 'event not found' });
          }
          event1.collaborators.push({ user: user._id });
          await event1.save();
          success = true;
          const dateString = event1.start;
          const dateObject = new Date(dateString);
          const formattedDate = dateObject.toLocaleDateString('en-GB', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
          });

          // Format time as 'hh:mm AM/PM'
          const formattedTime = dateObject.toLocaleTimeString('en-US', {
            hour: '2-digit',
            minute: '2-digit',
            hour12: true,
          });

          const distinct_id = user.email; 
          const event_name = "EVENTSHARED" 
          const properties = {        
            "recep":user.name,                  
            "owner":user2.name,
            "title":event1.title,
            "date":formattedDate,
            "time":formattedTime
          }  
          const event = new Event(distinct_id, event_name, properties)
          const response  = supr_client.track_event(event)
          response.then((res) => console.log("response", res));


          return res.json({ success, event1});


        } catch (error) {
          console.error(error.message);
          return res.status(500).send("some error occurred");
        }
      });
        
    

Edit Events

  
      app.post("/editevent/:id",fetchuser,async(req,res)=>{
        try {
          let event1 = await Events.findById(req.params.id);
          if (!event1) {
            return res.status(404).json({ message: "Event not found" });
          }
          event1.title = req.body.title;
          await event1.save();
          res.json({event1});
        } catch (error) {
          console.error(error.message);
          return res.status(500).send("some error occurred");
        }
      });
       /**********************listening on port **************************************/


      app.listen(port,()=>{
          console.log("server started on port 4000");
      })
        
    

For further information on workflows:  https://docs.suprsend.com/docs/node-trigger-workflow-from-api

Creating Templates and Worflows using SuprSend API

Once your backend is ready with SuprSend Node SDK, you can programatically create workflows from it. More details in here: Trigger Workflow from API

You can create templates from the dashboard itself.

Written by:
Sanjeev Kumar
Engineering, SuprSend
Implement a powerful stack for your notifications
By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.