<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: miminiverse</title>
    <description>The latest articles on Forem by miminiverse (@miminiverse).</description>
    <link>https://forem.com/miminiverse</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1137898%2F2266f351-ea3f-4a57-8234-96fbcc4ff4b0.jpeg</url>
      <title>Forem: miminiverse</title>
      <link>https://forem.com/miminiverse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/miminiverse"/>
    <language>en</language>
    <item>
      <title>How to log in with Google and set a schedule with Google Calendar in Express application</title>
      <dc:creator>miminiverse</dc:creator>
      <pubDate>Mon, 18 Sep 2023 00:01:25 +0000</pubDate>
      <link>https://forem.com/miminiverse/how-to-log-in-with-google-and-set-a-schedule-in-google-calendar-in-express-application-i80</link>
      <guid>https://forem.com/miminiverse/how-to-log-in-with-google-and-set-a-schedule-in-google-calendar-in-express-application-i80</guid>
      <description>&lt;p&gt;It will be handy when we can log in with our Google Account and set up any schedule with Google Calendar &lt;/p&gt;

&lt;p&gt;First, we will need to set up a little bit in the front end. In the login page in React, we add this function on onClick even Google sign-in button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleGoogleAuthUrl = () =&amp;gt; {
    const rootURL = "https://accounts.google.com/o/oauth2/v2/auth";
    const options = {
      redirect_uri: `${BASE_URL}/auth/google/callback`,
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
      access_type: "offline",
      response_type: "code",
      prompt: "consent",
      scope: [
        "https://www.googleapis.com/auth/userinfo.profile",
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/calendar",
      ].join(" "),
    };
    const qs = new URLSearchParams(options);
    window.location.assign(`${rootURL}?${qs.toString()}`);
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BASE_URL is the backend URL for example we can use &lt;a href="http://localhost:8000"&gt;http://localhost:8000&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the scope, we can add any other scopes provided by Google, in this case, these 3 are all we need.&lt;/p&gt;

&lt;p&gt;We use window.location.assign to redirect users to the constructed URL. We need to use the toString() method to format the object in a correct way, adding "=" between key and value pairs, and adding "&amp;amp;" between different parameters.&lt;/p&gt;

&lt;p&gt;Now let's move to our backend repo. In app.ts, add this line: &lt;/p&gt;

&lt;p&gt;app.get('/auth/google/callback', googleOauthHandler);&lt;/p&gt;

&lt;p&gt;Next, in controllers folder, create a file called OAuth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import User from '../models/User';
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import axios from 'axios';
import qs from 'qs';
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from '../config';

interface GoogleOauthToken {
  id_token: string;
  access_token: string;
  refresh_token: string;
}

const getGoogleOauthToken = async ({
  code,
}: {
  code: string;
}): Promise&amp;lt;GoogleOauthToken | undefined&amp;gt; =&amp;gt; {
  const url = 'https://oauth2.googleapis.com/token';
  const values = {
    code,
    client_id: GOOGLE_CLIENT_ID,
    client_secret: GOOGLE_CLIENT_SECRET,
    redirect_uri: `${BASE_URL}/auth/google/callback`,
    grant_type: 'authorization_code',
  };
  try {
    const data = await axios.post(url, qs.stringify(values), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    });

    return data.data;
  } catch (err) {
    console.log(err);
  }
};

const googleOauthHandler = async (req: Request, res: Response) =&amp;gt; {
  const code: any = req.query.code;

  try {
    if (!code) {
      res.status(400).send('Invalid authorization code');
      return;
    }

    const googleOauthToken = await getGoogleOauthToken({ code });

    if (!googleOauthToken) {
      res.status(401).send('Google OAuth token not found');
      return;
    }

    const { id_token } = googleOauthToken;
    const { refresh_token } = googleOauthToken;
    const OAuthToken = refresh_token;

    if (id_token &amp;amp;&amp;amp; OAuthToken) {
      const googleUser = jwt.decode(id_token) as {
        email?: string;
        name?: string;
        picture?: string;
      };

      const email = googleUser.email;
      try {
        const user = await User.findOne({ email });
        if (!user) {
          res.redirect('http://localhost:3000/login');
          return res.status(403).send('Account is not exist');
        }

        const token = user.createJWT();
        const refreshToken = user.createRefreshToken();

        await user.updateOne({ refreshToken, OAuthToken });

        res.cookie('token', refreshToken, {
          httpOnly: true,
          sameSite: 'none',
          secure: true,
          maxAge: 24 * 60 * 60 * 1000,
        });

        res.redirect('http://localhost:3000');

        return res.status(200).json({
          user: { name: user.name, userId: user._id, email: user.email },
          token,
        });
      } catch (err) {
        console.log(err);
      }
    }
  } catch (err) {
    console.log(err);
    res.redirect('http://localhost:3000/login');
  }
};

export default googleOauthHandler;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, assuming you already have a User model (if you do not please check our repo for reference), we will just need to add a few libraries&lt;/p&gt;

&lt;p&gt;We will start with googleOauthHandler. Remember when we press a sign-in button in the front end, google will return us a code that we can retrieve from req.query.code. We will need that code to get our user's data, and in this case I specifically looking for the token.&lt;/p&gt;

&lt;p&gt;We will make a POST request to &lt;code&gt;https://oauth2.googleapis.com/token.&lt;/code&gt; We will be able to retrieve id_token from Google and then we can use jwt.decode to get all of the info we need. &lt;/p&gt;

&lt;p&gt;We will then compare the Google email and the email in our database, if it matches we can confirm that the user exists.&lt;/p&gt;

&lt;p&gt;The way our application is set up will only allow users to sign in if there's an email already existing in the database, if there's no email found, we will simply redirect users back to log-in page and we will not create a new user.&lt;/p&gt;

&lt;p&gt;We will also need to retrieve the refersh_token from Google, set it as OAuthToken in User model and use it later for the Google Calendar.&lt;/p&gt;

&lt;p&gt;Finally, since we use JWT to authenticate user, we need to create one and set a cookie to authenticate that user similar to when we sign in regular user.&lt;/p&gt;

&lt;p&gt;If there's no token exists, we will redirect the user back to the login page&lt;/p&gt;

&lt;p&gt;Next, we will move to the createSchedule function, first we need to import the below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { google } from 'googleapis';
import moment from 'moment-timezone';
import User from '../models/User';
import {
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  GOOGLE_CLIENT_URL,
} from '../config';

const oauth2Client = new google.auth.OAuth2(
  GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET,
  GOOGLE_CLIENT_URL
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these below will be in your .env file, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOGLE_CLIENT_ID=e54954459925-ejol4bm9t5hknr.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPdX-YQEQhFv-2Bztwem9CeVBsdfsdfdsfJEKyvSCV
GOOGLE_CLIENT_URL=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our project, we use TypeScript so we add types, you don't need to do so if you don't use TypeScript&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const scheduleEvent = async ({
  summary,
  start,
  end,
  email,
  description,
}: {
  summary: string;
  start: Date;
  end: Date;
  email: string;
  description: string;
}) =&amp;gt; {
  const user = await User.findOne({ email });
  if (!user) {
    throw new Error('error');
  }
  let new_refresh_token;
  if (user?.OAuthToken) {
    new_refresh_token = user.OAuthToken;
  }

  const formattedStart = moment(start).format('YYYY-MM-DDTHH:mm:ssZ');
  const formattedEnd = moment(end).format('YYYY-MM-DDTHH:mm:ssZ');

  oauth2Client.setCredentials({
    refresh_token: new_refresh_token,
  });
  const calendar = google.calendar('v3');
  const response = await calendar.events.insert({
    auth: oauth2Client,
    calendarId: 'primary',
    requestBody: {
      summary: summary,
      description: description,
      start: {
        dateTime: formattedStart,
      },
      end: {
        dateTime: formattedEnd,
      },
    },
  });
  if (!response) return 'Events failed to save in your Google Calendar';
  return 'Events successfully saved in your Google Calendar';
};

export default scheduleEvent;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we need to check if the user is in our database, then we retrieve their OAuthToken. Then we will just need the start &amp;amp; end time for the event, and the description is optional&lt;/p&gt;

&lt;p&gt;In this case, I'm using moment which is a date time library but if you don't want to use an external library, new Date() from Javascript would suffice&lt;/p&gt;

&lt;p&gt;This is the link to our demo as well as the repos below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/2UqX1BSH0hU"&gt;https://youtu.be/2UqX1BSH0hU&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Mentor-Up/server"&gt;https://github.com/Mentor-Up/server&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>“Talckatoo: Your Real-Time Multilingual Chat Solution Built with SocketIO, Express.js, and React!” — Part 3</title>
      <dc:creator>miminiverse</dc:creator>
      <pubDate>Thu, 24 Aug 2023 16:16:27 +0000</pubDate>
      <link>https://forem.com/miminiverse/talckatoo-your-real-time-multilingual-chat-solution-built-with-socketio-expressjs-and-react-part-3-40fm</link>
      <guid>https://forem.com/miminiverse/talckatoo-your-real-time-multilingual-chat-solution-built-with-socketio-expressjs-and-react-part-3-40fm</guid>
      <description>&lt;p&gt;In part 2, we built the backend of our chat application, which includes the functionality for real-time multilingual translations. In this part, we will focus on the front end, and specifically how to render all the messages in each conversation, and how to use Socket.IO to get real-time functionalities.&lt;/p&gt;

&lt;p&gt;We developed a beginner-friendly approach that uses only React hooks (useState, useContext) for state management, TypeScript, and CSS Tailwind.&lt;/p&gt;

&lt;p&gt;In our Chat App, we use React Router DOM to control all the routes. This allows us to easily navigate users between different parts of our application.&lt;/p&gt;

&lt;p&gt;We have three main routes in our application:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/:&lt;/strong&gt; This route is for user registration and login.&lt;br&gt;
&lt;strong&gt;/chat:&lt;/strong&gt; This route is for the main chat page.&lt;br&gt;
&lt;strong&gt;/profile:&lt;/strong&gt; This route is for user profile updates.&lt;/p&gt;

&lt;p&gt;We use the &lt;strong&gt;useNavigate&lt;/strong&gt; hook to navigate users between these routes. This hook makes it easy to navigate to a specific route or to go back to the previous route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const App = () =&amp;gt; {
 const navigate = useNavigate();
 useEffect(() =&amp;gt; {
   if (localStorage.getItem("token")) {
     navigate("/chat");
   } else {
     navigate("/");
   }
 }, []);

 return (
   &amp;lt;&amp;gt;
     &amp;lt;div className="flex flex-col h-screen "&amp;gt;
       &amp;lt;Navbar /&amp;gt;
       &amp;lt;Routes&amp;gt;
         &amp;lt;Route path="/" element={&amp;lt;Main /&amp;gt;} /&amp;gt;
         &amp;lt;Route path="/chat" element={&amp;lt;Chat /&amp;gt;} /&amp;gt;
         &amp;lt;Route path="/profile" element={&amp;lt;Profile /&amp;gt;} /&amp;gt;
       &amp;lt;/Routes&amp;gt;
     &amp;lt;/div&amp;gt;
   &amp;lt;/&amp;gt;
 );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll skip over the sign-up and login functions and concentrate on the core aspect of handling messages with SocketIO. If you'd like to explore the complete codebase, you can find it in our repository.&lt;/p&gt;

&lt;p&gt;Once a user logs in, we retrieve and store their data using the useContext hook. This allows us to seamlessly integrate user information into the chat interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const {
   user,
   conversationId,
   setConversationId,
   selectId,
   setSelectId,
   isDarkMode,
   setRecipient,
   messages,
   language,
   setLanguage,
 } = useContext(UserContext);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Connecting to the Socket.IO Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this part of the code, we're using useRef to store the socket instance. This avoids unnecessary re-renders when the component updates. The socket is connected to the backend URL using the io function from the socket.io-client library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const socket = useRef&amp;lt;Socket&amp;lt;MyEventMap&amp;gt; | null&amp;gt;();
 const [usersList, setUsersList] = useState&amp;lt;UsersList | null&amp;gt;(null);
 const [onlineFriends, setOnlineFriends] = useState&amp;lt;User[]&amp;gt;([]);
 const [onlineUsers, setOnlineUsers] = useState&amp;lt;string[]&amp;gt;([]);
 const [view, setView] = useState&amp;lt;"friends" | "people"&amp;gt;("friends");
first of all, we need to import socket io client and instantiate the Socket. The socket will be connected to your back end, it could be “http://localhost:8000”. In our case, we connect it to our backend URL which is deployed on Render
 useEffect(() =&amp;gt; {
   socket.current = io(`${DOMAIN.BACKEND_DEPLOY_URL}`);
 }, []);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we would like to have insights into who's online and who's not. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a Living Connection from Front End&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user logs into the application, they connect to our application through a Socket.IO socket, then a simple command, socket.emit("addUser"), is executed on the frontend. This sends a clear signal to the backend that a new user has entered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server-side Magic with "addUser"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the backend, the server is on the lookout for the "addUser" event with socket.on("addUser"). The server records all of the newly arrived users in an array.&lt;/p&gt;

&lt;p&gt;The backend then emit back an event called "getUsers." This event is broadcasted using io.emit("getUsers")&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend Listening to the Pulse of "getUsers"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the front end, we use socket.on("getUsers") so we will always listen to the "getUsers" event. Whenever a new event reaches the front end, the online user list is automatically updated. &lt;/p&gt;

&lt;p&gt;Backend&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  socket.on("addUser", (userId: any) =&amp;gt; {
    onlineUsers.set(userId, socket.id);
    io.emit("getUsers", Array.from(onlineUsers));
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frontend&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    if (socket.current &amp;amp;&amp;amp; user) {
      socket.current.emit("addUser", user._id);
      socket.current.on("getUsers", (users: unknown[]) =&amp;gt; {
        let usersMap = new Set();
        users.map((user: any) =&amp;gt; {
          usersMap.add(user[0]);
          let usersArray: any[] = Array.from(usersMap);
          setOnlineUsers(usersArray);
        });
      });
    }
  }, [socket.current, user]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To put it simply, when we work on the part of a website that you see, there are two main ways we use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Front end&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending Messages: Imagine you want to tell the backstage of the website something. To do that, we use something like a magic messenger called "socket.emit." It's like sending a secret note. You can give your note any name you like. For example, I use "addUser" to say a new person joined. But you could also say "newUser," just remember to use the same name backstage and frontstage.&lt;/li&gt;
&lt;li&gt;Listening for Messages: Now, when the backstage talks back to us, we use "socket.on" to listen to the messages. All these messages need the same name, so we know what they mean and which note we are talking about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, when we go backstage, things are a bit different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Sending Messages: Now, we have two choices: "socket.emit" or "io.emit." It's like choosing how to shout out news to everyone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;socket.emit: This's like telling something directly to one person in the crowd.&lt;/li&gt;
&lt;li&gt;io.emit: This's like shouting out the news so everyone in the crowd can hear.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Listening for Messages: Here, we still use "socket.on" just like in the front end. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, to wrap it all up, remember these simple tricks to make the front and backstage of the website chat smoothly and share all the important news!&lt;/p&gt;

&lt;p&gt;In our app, we went with io.emit. We'll tell you more about why in the next post.&lt;/p&gt;

&lt;p&gt;For all the details about our project, check out our place on the internet: &lt;a href="https://github.com/Talckatoo"&gt;https://github.com/Talckatoo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep an eye out for Part 4, where we'll bring you more cool stuff!&lt;/p&gt;

</description>
      <category>socketio</category>
      <category>messaging</category>
      <category>language</category>
      <category>react</category>
    </item>
    <item>
      <title>“Talckatoo: Your Real-Time Multilingual Chat Solution Built with SocketIO, Express.js, and React!” — Part 2</title>
      <dc:creator>miminiverse</dc:creator>
      <pubDate>Tue, 15 Aug 2023 02:36:16 +0000</pubDate>
      <link>https://forem.com/miminiverse/talckatoo-your-real-time-multilingual-chat-solution-built-with-socketio-expressjs-and-react-part-2-2h35</link>
      <guid>https://forem.com/miminiverse/talckatoo-your-real-time-multilingual-chat-solution-built-with-socketio-expressjs-and-react-part-2-2h35</guid>
      <description>&lt;p&gt;In the previous part, I provided an overview of our powerful chat application which offers real-time multilingual translations.&lt;/p&gt;

&lt;p&gt;In this part, we will delve into the backend how we set up all the routes, and also how we use socketIO to get real-time functionalities.&lt;/p&gt;

&lt;p&gt;Our application revolves around three essential models: User, Conversation, and Message.&lt;/p&gt;

&lt;p&gt;In essence, our route structure is designed to achieve our ultimate goal of displaying messages. This involves a series of HTTP requests:&lt;/p&gt;

&lt;p&gt;Making a POST request to authenticate and log in the user&lt;br&gt;
Making a GET request to get comprehensive information about other users.&lt;br&gt;
Making a GET to fetch both the conversation and associated messages&lt;br&gt;
Let’s start with the User model. Within this model, each user will maintain a list of their conversations with others. Additionally, users will be able to set their preferred translated language during sign-up, thus enhancing the multilingual communication experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const UserSchema = new Schema&amp;lt;Iuser&amp;gt;({
  userName: {
    type: String,
    unique: true,
    required: true,
    minLength: 5,
  },
  email: {
    type: String,
    required: true,
    unique: true,
    validate: [validator.isEmail, "please enter a valid email address"],
  },
  password: {
    type: String,
    minlength: 5,
  },
  conversations: [
    {
      type: Schema.Types.ObjectId,
      ref: "Conversation",
    },
  ],
  profileImage: {
    public_id: String,
    url: String,
  },
  language: {
    type: String,
  },
  welcome: {
    type: String,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving on to the Conversation model, it serves as a container for all the messages exchanged within a particular conversation. Moreover, the model also associates the users who are part of that conversation. This setup ensures that messages are organized and accessible within the context of their relevant discussions, while maintaining a clear link to the users involved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const conversationSchema = new Schema&amp;lt;Iconversation&amp;gt;(
  {
    messages: [
      {
        type: Schema.Types.ObjectId,
        ref: "Message",
      },
    ],
    users: [
      {
        type: Schema.Types.ObjectId,
        ref: "User",
      },
    ],
  },
  { timestamps: true }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, let’s look at the Message model. An important aspect of this model is that it includes the sender’s information. This addition proves invaluable in distinguishing between the sender and recipient of each message, which in turn facilitates the seamless configuration of APIs and enhances the overall communication process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const messageSchema = new Schema&amp;lt;Imessage&amp;gt;(
  {
    message: {
      type: String,
      minlength: 1,
      maxlength: 2000,
    },
    sender: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    voiceNote: {
      public_id: String,
      url: String,
    },
  },
  { timestamps: true }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving forward to the controllers, where we’ll navigate through the implementation of various routes.&lt;/p&gt;

&lt;p&gt;While I’ll skip detailing the signup, login, and user profile update routes, I’d like to highlight the user-related routes. Specifically, we’ll discuss the process of fetching users from our database.&lt;/p&gt;

&lt;p&gt;One of the very first questions, how can we display the friend lists?&lt;/p&gt;

&lt;p&gt;In response, we devised a strategic approach: categorization of users into two distinct groups: those who are logged in and have never been contacted, and those with whom you’ve engaged in previous conversations. This segmentation enhances the user experience and helps organize interactions effectively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
exports.getUsers = catchAsync(
  async (req: Request, res: Response, next: NextFunction) =&amp;gt; {
    const { userId }: any = req.user;
    const currentUser = await User.findOne({ _id: userId });

    const populateOptions = {
      path: "conversations",
      select: "_id createdAt updatedAt",
    };

    const contactedUsers = await User.find({
      _id: { $ne: currentUser._id },
      conversations: { $in: currentUser.conversations },
    })
      .select("_id userName conversations profileImage language")
      .populate(populateOptions);

    contactedUsers.forEach((user: any) =&amp;gt; {
      user.conversations = user.conversations.filter((conversation: any) =&amp;gt; {
        return currentUser.conversations.includes(conversation._id);
      });
    });

    const modifiedUsers = contactedUsers.map((user: any) =&amp;gt; {
      return {
        _id: user._id,
        userName: user.userName,
        profileImage: user.profileImage,
        conversation: user.conversations[0],
        conversations: undefined,
        language: user.language,
      };
    });

    modifiedUsers.sort((a: any, b: any) =&amp;gt; {
      if (
        a.conversation["updatedAt"].getTime() 
        b.conversation["updatedAt"].getTime()
      ) {
        return 1;
      }

      if (
        a.conversation["updatedAt"].getTime() 
        b.conversation["updatedAt"].getTime()
      ) {
        return -1;
      }

      return 0;
    });

    const uncontactedUsers = await User.find({
      _id: { $ne: currentUser._id },
      conversations: { $nin: currentUser.conversations },
    }).select("_id userName profileImage language");

    if (contactedUsers.length &amp;lt; 1 &amp;amp;&amp;amp; uncontactedUsers.length &amp;lt; 1) {
      res
        .status(200)
        .json({ status: "Success", message: "There are currently no users" });
    }

    res.status(200).json({
      status: "Success",
      users: { contactedUsers: modifiedUsers, uncontactedUsers },
    });
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this point onward, we can retrieve the unique conversation IDs and seamlessly showcase each individual conversation, which facilitates the comprehensive display of all messages that our logged-in user has exchanged with others.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
exports.getUserConversation = catchAsync(
  async (req: Request, res: Response, next: NextFunction) =&amp;gt; {
    const { conversationId } = req.params;

    const populateOptions = [
      { path: "users", select: "userName profileImage language" },
      { path: "messages", select: "message sender createdAt voiceNote" },
    ];

    const conversation = await Conversation.findOne({
      _id: conversationId,
    }).populate(populateOptions);

    if (!conversation) {
      throw new AppError("This conversation does not exist", 404);
    }

    res.status(200).json({ status: "Success", conversation });
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Equally crucial is the route dedicated to creating new messages, a feature that defines the essence of our application. Through this functionality, users can receive translated versions of their messages, aligned with their chosen language preference in their profiles.&lt;/p&gt;

&lt;p&gt;Messages are classified into two types: text messages and translated voice messages, with the latter being stored using Cloudinary to store URLs for easy retrieval and playback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
exports.createMessage = catchAsync(
  async (req: Request, res: Response, next: NextFunction) =&amp;gt; {
    const {
      message: text,
      to,
      from,
      targetLanguage,
      voiceToVoice,
      voiceTargetLanguage,
    } = req.body;
    const target = targetLanguage ? targetLanguage : "en";

    if (!text || !to || !from) {
      throw new AppError("Invalid Input. Please try again", 400);
    }

    if (to === from) {
      throw new AppError("You can't send a message to yourself", 403);
    }

    const options = {
      method: "POST",
      url: process.env.TRANSLATE_URL,
      headers: {
        "content-type": "application/json",
        "X-RapidAPI-Key": process.env.TRANSLATE_API_KEY,
        "X-RapidAPI-Host": process.env.API_HOST,
      },
      data: {
        text,
        target,
      },
    };
    const response = await axios.request(options);

    let translate: string;

    if (response.data[0].result.ori === "en" &amp;amp;&amp;amp; target === "en") {
      translate = "";
    } else {
      translate = `\n${response.data[0].result.text}`;
    }

    if (!voiceToVoice) {
      const message = await Message.create({
        message: text + translate,
        sender: from,
      });

      let conversation = await Conversation.findOneAndUpdate(
        { users: { $all: [from, to] } },
        { $push: { messages: message.id } }
      );

      if (!conversation) {
        conversation = await Conversation.create({
          messages: [message.id],
          users: [from, to],
        });

        await User.findOneAndUpdate(
          { _id: from },
          { $push: { conversations: conversation.id } }
        );

        await User.findOneAndUpdate(
          { _id: to },
          { $push: { conversations: conversation.id } }
        );
      }
      res.status(201).json({ status: "Success", message, conversation });
    } else {
      const encodedParams = new URLSearchParams();
      encodedParams.set("src", translate);
      encodedParams.set("hl", voiceTargetLanguage);
      encodedParams.set("r", "0");
      encodedParams.set("c", "mp3");
      encodedParams.set("f", "8khz_8bit_mono");
      encodedParams.set("b64", "true");
      const options = {
        method: "POST",
        url: process.env.URL,
        params: {
          key: process.env.VOICE_PARAMS_KEY,
        },
        headers: {
          "content-type": "application/x-www-form-urlencoded",
          "X-RapidAPI-Key": process.env.VOICE_API_KEY,
          "X-RapidAPI-Host": process.env.VOICE_API_HOST,
        },
        data: encodedParams,
      };

      const response = await axios.request(options);

      const audioData = response.data;

      const decodedData = Buffer.from(audioData, "base64");

      // Create a temporary file to store the audio data
      const tempFilePath = "./temp_audio.mp3";
      fs.writeFileSync(tempFilePath, decodedData);

      const uploadOptions = {
        resource_type: "video",
        format: "mp3",
        folder: "voice-notes",
      };

      cloudinary.uploader.upload_large(
        tempFilePath,
        uploadOptions,
        async (error: any, result: any) =&amp;gt; {
          // Delete the temporary file
          fs.unlinkSync(tempFilePath);

          if (error) {
            console.log(error);
          } else {
            const { public_id, url } = result;
            const message = await Message.create({
              voiceNote: { public_id, url },
              sender: from,
            });

            let conversation = await Conversation.findOneAndUpdate(
              { users: { $all: [from, to] } },
              { $push: { messages: message.id } }
            );

            if (!conversation) {
              conversation = await Conversation.create({
                messages: [message.id],
                users: [from, to],
              });

              await User.findOneAndUpdate(
                { _id: from },
                { $push: { conversations: conversation.id } }
              );

              await User.findOneAndUpdate(
                { _id: to },
                { $push: { conversations: conversation.id } }
              );
            }
            res.status(201).json({ status: "Success", message, conversation });
          }
        }
      );
    }
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to send a voice message, you can use the route below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
exports.createVoiceNote = catchAsync(
  async (req: Request, res: Response, next: NextFunction) =&amp;gt; {
    const form = new multiparty.Form();

    form.parse(req, async (err: any, fields: any, files: any) =&amp;gt; {
      const { to, from } = fields;

      if (!to || !from) {
        throw new AppError("Invalid Input. Please try again", 400);
      }

      if (to === from) {
        throw new AppError("You can't send a message to yourself", 403);
      }

      if (files.audio) {
        const data = await cloudinary.uploader.upload(files.audio[0].path, {
          resource_type: "video",
          folder: "voice-notes",
        });
        const { public_id, url } = data;
        const message = await Message.create({
          voiceNote: { public_id, url },
          sender: from,
        });

        let conversation = await Conversation.findOneAndUpdate(
          { users: { $all: [from, to] } },
          { $push: { messages: message.id } }
        );

        if (!conversation) {
          conversation = await Conversation.create({
            messages: [message.id],
            users: [from, to],
          });

          await User.findOneAndUpdate(
            { _id: from },
            { $push: { conversations: conversation.id } }
          );

          await User.findOneAndUpdate(
            { _id: to },
            { $push: { conversations: conversation.id } }
          );
        }
        res.status(201).json({ message });
      }
    });
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally here comes the best part: infusing the app with real-time capabilities through SocketIO. Before delving into the mechanics, let’s unravel the rationale behind employing SocketIO and the reasons that set it apart from traditional real-time communication implemented through REST APIs.&lt;/p&gt;

&lt;p&gt;1/ REST APIs operate in a stateless manner, treating each GET or POST request in isolation without maintaining any continuous connection.&lt;br&gt;
→ In our context, where we’re striving for REAL-TIME updates, using REST APIs would necessitate incessant requests to the server,&lt;br&gt;
even if there might not be new messages. This approach is resource-intensive and inefficient.&lt;/p&gt;

&lt;p&gt;2/ Enter SocketIO, empowering us to establish a persistent connection between two users that remains active until the users opt to disconnect.&lt;br&gt;
The server, in turn, automatically updates and pushes new messages to the users without necessitating a site reload or any additional actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
io.on("connection", (socket: Socket) =&amp;gt; {
  socket.on("addUser", (userId: any) =&amp;gt; {
    onlineUsers.set(userId, socket.id);
    io.emit("getUsers", Array.from(onlineUsers));
  });

  socket.on("sendMessage", (data: any) =&amp;gt; {
    const sendUserSocket = onlineUsers.get(data.to);
    if (sendUserSocket) {
      io.to(sendUserSocket).emit("getMessage", data);
    }
  });

  socket.on("disconnect", () =&amp;gt; {
    // Remove the disconnected socket from onlineUsers map
    for (const [userId, socketId] of onlineUsers) {
      if (socketId === socket.id) {
        onlineUsers.delete(userId);
        break;
      }
    }
    io.emit("getUsers", Array.from(onlineUsers));
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certainly, let’s break down the process. Firstly, we’ll initiate the connection using the io.on("connection") method, a built-in feature of SocketIO.&lt;/p&gt;

&lt;p&gt;The following code snippet helps us track users who are online. Whenever a user connects to our app (by logging in), their ID is sent from the front end to SocketIO, and we manage them through a Map.&lt;/p&gt;

&lt;p&gt;It’s crucial to use a Map to avoid user duplication. When we emit the data back to the front end, we’ll also need to convert it to an array, as Maps and Sets need manual serialization for compatibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const onlineUsers = new Map();
socket.on("addUser", (userId: any) =&amp;gt; {
    onlineUsers.set(userId, socket.id);
    io.emit("getUsers", Array.from(onlineUsers));
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nextfour lines of code are crucial for enabling real-time messaging in our application.&lt;/p&gt;

&lt;p&gt;As a quick recap, we have a Map named onlineUsers that keeps track of connected users. Each user has a unique socket ID associated with them. When a user successfully connects, they receive a socket ID.&lt;/p&gt;

&lt;p&gt;Here’s how the messaging process works:&lt;/p&gt;

&lt;p&gt;When you want to send a message to a specific user, you send the recipient’s ID from the front end to the backend as data.&lt;br&gt;
With the recipient’s ID, you can retrieve their socket ID from the onlineUsers map.&lt;br&gt;
Once you have the recipient’s socket ID, you emit the message to that socket ID, which effectively sends the message to the intended recipient.&lt;br&gt;
This process ensures that messages are sent directly to the recipient in real-time. Here’s the basic structure of how this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;socket.on("sendMessage", (data: any) =&amp;gt; {
    const sendUserSocket = onlineUsers.get(data.to);
    if (sendUserSocket) {
      io.to(sendUserSocket).emit("getMessage", data);
    }
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, when a user disconnects from the app, we can use socket.on("disconnect")to remove them from the list of online users and emit the updated list to the frontend to reflect the current online status of users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;socket.on("disconnect", () =&amp;gt; {
    // Remove the disconnected socket from onlineUsers map
    for (const [userId, socketId] of onlineUsers) {
      if (socketId === socket.id) {
        onlineUsers.delete(userId);
        break;
      }
    }
    io.emit("getUsers", Array.from(onlineUsers));
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find comprehensive information about error handling, CORS configuration, database integration, and the array of packages employed throughout our project by exploring our repository at: &lt;a href="https://github.com/Talckatoo"&gt;https://github.com/Talckatoo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 3 on how we’ve built the front-end aspects of our project.&lt;/p&gt;

</description>
      <category>chatapp</category>
      <category>realtime</category>
      <category>translate</category>
      <category>opensource</category>
    </item>
    <item>
      <title>“Talckatoo: Your Real-Time Multilingual Chat Solution Built with SocketIO, Express.js, and React!”</title>
      <dc:creator>miminiverse</dc:creator>
      <pubDate>Sat, 12 Aug 2023 15:48:21 +0000</pubDate>
      <link>https://forem.com/miminiverse/talckatoo-your-real-time-multilingual-chat-solution-built-with-socketio-expressjs-and-react-2kne</link>
      <guid>https://forem.com/miminiverse/talckatoo-your-real-time-multilingual-chat-solution-built-with-socketio-expressjs-and-react-2kne</guid>
      <description>&lt;p&gt;We’re a team of dynamic individuals who recently graduated from a coding BootCamp. Our inspiration stems from the language barrier issue, and we’ve taken on the challenge to address it directly. The result is Talckatoo, a chat app we’ve developed using SocketIO, Express.js, and React. Our goal? To dissolve language barriers through real-time translation, enabling meaningful connections across diverse cultures.&lt;/p&gt;

&lt;p&gt;What issues does our app resolve?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Language Barrier: Talckatoo provides seamless language translation, making it easier for individuals from different language backgrounds, especially elderly users who might not be familiar with translation tools. This feature reduces communication obstacles and saves time.&lt;/li&gt;
&lt;li&gt;Integrated ChatGPT: Our app integrates ChatGPT, allowing users to access its capabilities without leaving the app. This means they can enjoy AI-powered assistance without any extra hassle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text messaging with real-time translation&lt;/li&gt;
&lt;li&gt;Voice-enabled transcription and messaging&lt;/li&gt;
&lt;li&gt;AI-powered “&lt;a class="mentioned-user" href="https://dev.to/birdie"&gt;@birdie&lt;/a&gt;” bot for user inquiries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our backend tech stack: Node.js, Express.js, MongoDB, SocketIO, Typescript, chatGPT, Rapid API&lt;/p&gt;

&lt;p&gt;Our frontend tech stack: React, Tailwind CSS, Typescript, WhisperAI&lt;/p&gt;

&lt;p&gt;We’re excited to delve into the details of how we created this app in the next part.&lt;/p&gt;

&lt;p&gt;You can experience the live version of Talckatoo at &lt;a href="https://talckatoo.me"&gt;https://talckatoo.me&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We welcome your feedback and suggestions, so feel free to get in touch. If you’re curious about the nitty-gritty, you can find our code repository here: &lt;a href="https://github.com/Talckatoo"&gt;https://github.com/Talckatoo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>react</category>
      <category>node</category>
      <category>socketio</category>
    </item>
  </channel>
</rss>
