Build a Chat App with Cloudinary and Xata

nefejames - Nov 23 '22 - - Dev Community

One common thing with platforms like WhatsApp, Telegram, Twitter, and Instagram is that they all offer great chat functionality via their mobile and web applications. The ability to communicate swiftly with people across the globe has never been more important.

In this article, we‘ll learn how to create a chat application with Next.js, Cloudinary's Upload widget, and Xata.

Demo and Source Code

View the live demo via this link and access the source code on GitHub.

Prerequisites

To follow along with this article, we need to have:

What Is Xata?

Xata is a serverless database built on top of PostgreSQL and Elasticsearch. It comes with useful integrations with popular frameworks and technologies like Next.js, Netlify, Nuxt.js, Vercel, Remix, and SvelteKit.

What is Cloudinary?

Cloudinary is a cloud-based and end-to-end platform with tools and functionalities for storing, processing, editing, managing, delivering, and transforming media assets.

What We Will Create

The screenshot below shows the application we will create.
Users can fill in their name, provide a message and upload their avatar through the form on the right. Upon clicking the Send button, the data gets saved to Xata’s database. We fetch the chats from the database and display them on the left side of the application.
The avatar image upload is handled by Cloudinary’s Upload Widget.

the chat app we will create

Getting Started

Download, clone, or fork this starter template that contains the UI for the chat app.

    npx create-next-app xata-cloudinary-chat-app -e https://github.com/nefejames/hackmamba-xata-chat-app-ui
    or
    yarn create next-app xata-cloudinary-chat-app -e https://github.com/nefejames/hackmamba-xata-chat-app-ui
Enter fullscreen mode Exit fullscreen mode

Next, navigate to the project directory, and run the command below to install Chakra UI, Next.js, Xata’s SDK, and every other necessary dependency, then run the application.

    npm i
Enter fullscreen mode Exit fullscreen mode

The starter template consists of the following files:

  • components/ChatContainer.js: the container that holds the chats
  • components/ChatInput.js: where users can input and submit new chats
  • components/ChatProfile.js: holds each user’s name, avatar, and chat message
  • layout/index.js: the layout for the application
  • utils: will hold the Xata instance and avatar upload logic, which we will set up later

Setting up the Xata Database

We start by creating a new database called chat-app on our Xata account.

creating a new database in Xata

Next, create a messages table that will contain the records of the chat messages that users submit. Each record will have message, author, img, and id properties; the id is automatically generated by Xata.
We got the images from the media uploads in our Cloudinary account.

creating the messages records

The database schema is ready, so let’s set up the Xata instance next, and integrate it into the application.

Setting up the Xata Instance

Run the command below to install the CLI globally.

    npm install @xata.io/cli -g
Enter fullscreen mode Exit fullscreen mode

Next, run xata auth login, which will prompt you to Create a new API key in the browser or use an existing one; we’ll go with the first option.

login to create CLI API key

Once in the browser, give the API a name and click the Create API key button.

creating a Xata API ley

Back on the dashboard, click on the Get Code Snippet button at the top right corner and copy the second command. We will use it to initialize the project locally with the aid of the CLI.

Run the command in the terminal and choose from the configuration options. The screenshot below shows the configurations for this application.

xata cli config

The CLI will automatically create a xata.js file that contains the XataClient instance we need; let’s move on to the coding aspects of the application.

Fetching the Messages in the Database

We’ll start by fetching the messages that already exist in the database. Update the index.js file with the following code:

    import { Box, Flex, Heading } from "@chakra-ui/react";
    import ChatContainer from "@components/ChatContainer";
    import ChatInput from "@components/ChatInput";
    import { getXataClient } from "@utils/xata";

    export default function Home({ data }) {
      return (
        <div>
          <Heading as="h1" mb={5}>
            Xata Chat App
          </Heading>
          <Flex flexDir={["column", "row"]} justifyContent="space-between">
            <ChatContainer chats={data} />
            <Box>
              <ChatInput />
            </Box>
          </Flex>
        </div>
      );
    }

    export async function getServerSideProps() {
      const xata = getXataClient();
      const data = await xata.db.messages.getAll();
      return { props: { data } };
    }
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Imported the getXataClient utility and initialized a new instance
  • Queried the messages table and fetched all records with getServerSideProps
  • Passed the fetched data to the ChatContainer component through props

Displaying the Messages in the ChatContainer Component

Here, we then loop through the data and display the individual messages in the UI.

    import { Box, VStack } from "@chakra-ui/react";
    import ChatProfile from "./ChatProfile";

    export default function ChatContainer({ chats }) {
      return (
        <Box>
          <VStack spacing={5}>
            {chats.map((chat) => (
              <ChatProfile
                key={chat.id}
                author={chat.author}
                img={chat.img}
                message={chat.message}
              />
            ))}
          </VStack>
        </Box>
      );
    }
Enter fullscreen mode Exit fullscreen mode

Setting up the Avatar Upload Functionality in the ChatInput Component

When a user clicks on the Upload Avatar button, Cloudinary’s Upload Widget will open, enabling the user to upload a picture; let’s set it up. Update the ChatInput.js file with the following code:

    import { useState } from "react";
    import { Box, Button, Input, Flex } from "@chakra-ui/react";
    import ShowImageUploadWidget from "@utils/upload";

    export default function ChatInput() {
      const [imgUrl, setImgUrl] = useState(null);

      return (
        <Box w={["full", "400px"]}>
          <form>
            <Flex flexDir="column">
              <Input name="author" placeholder="Author" />
              <Input name="newMessage" placeholder="Message" />
              <Button
                onClick={() => ShowImageUploadWidget(setImgUrl)}
              >
                Upload Avatar
              </Button>
              <Button type="submit">Send</Button>
            </Flex>
          </form>
        </Box>
      );
    }
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Set up an imgUrl state that will contain the URL of the avatar users upload to Cloudinary
  • Imported a ShowImageUploadWidget function from the utils directory
  • Passed ShowImageUploadWidget to the Upload Avatar button’s onClick event handler

The ShowImageUploadWidget function is responsible for the upload functionality. Create a upload.js file and paste the following code to set up ShowImageUploadWidget.

    function ShowImageUploadWidget(setImgUrl) {
      window.cloudinary
        .createUploadWidget(
          {
            cloudName: "your-cloud-name",
            uploadPreset: "ml_default",
          },
          (error, result) => {
            if (!error && result && result.event === "success") {
              setImgUrl(result.info.thumbnail_url);
            }
            if (error) {
              console.log(error);
            }
          }
        )
        .open();
    }

    export default ShowImageUploadWidget;
Enter fullscreen mode Exit fullscreen mode


JavaScript

Let’s breakdown the snippet above:

  • ShowImageUploadWidget calls the createUploadWidget that exists in the cloudinary object
  • createUploadWidget takes in our cloudname as part of its config
  • ShowImageUploadWidget accepts the setImgUrl function as an argument. If an avatar is uploaded successfully, the imgUrl state will be updated with the URL of the avatar a user uploads

We have to load the widget’s script in order for it to work. Next.js provides a [Script](https://nextjs.org/docs/basic-features/script) component that we can use to load third-party scripts in our application.

Update the _app.js file with the following code:

    import { ChakraProvider } from "@chakra-ui/react";
    import Wrapper from "@layout/index";
    import Script from "next/script";

    function MyApp({ Component, pageProps }) {
      return (
        <ChakraProvider>
          <Script
            src="https://upload-widget.cloudinary.com/global/all.js"
            type="text/javascript"
            strategy="beforeInteractive"
          />
          <Wrapper>
            <Component {...pageProps} />
          </Wrapper>
        </ChakraProvider>
      );
    }

    export default MyApp;
Enter fullscreen mode Exit fullscreen mode

Here, we imported the Script component from Next.js and used it to load the widget’s script.

Setting up the Form Submission Functionality in the ChatInput Component

The form submission functionality is the last thing we need to set up. Update the ChatInput component with the following code:

    import { useState } from "react";
    import { Box, Button, Input, Flex, useToast } from "@chakra-ui/react";
    import ShowImageUploadWidget from "@utils/upload";

    export default function ChatInput() {
      const toast = useToast();
      const [imgUrl, setImgUrl] = useState(null);

      const handleSubmit = async (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);
        const author = data.author;
        const newMessage = data.newMessage;
        fetch("/api/add-message", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            author,
            newMessage,
            imgUrl,
          }),
        });
        e.target.reset();
        toast({
          title: "Message created.",
          description: "Message sent to chat db.",
        });
      };

      return (
        <Box w={["full", "400px"]}>
          <form onSubmit={handleSubmit}>
            <Flex flexDir="column">
              <Input name="author" placeholder="Author" />
              <Input name="newMessage" placeholder="Message" />
              <Button>
                Upload Avatar
              </Button>
              <Button type="submit">Send</Button>
            </Flex>
          </form>
        </Box>
      );
    }
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Created a handleSubmit function that takes the form data and passes it on the add-messages route. The add-messages will handle pushing the form data to the Xata database; we will set up the route next
  • Used the toast method to display a message upon successful form submission
  • Passed handleSubmit to the form’s onSubmit event handler

For the API route, create a api/add-message.js file and paste the following code:

    import { getXataClient } from "@utils/xata";
    const xata = getXataClient();

    const handler = async (req, res) => {
      const { author, newMessage, imgUrl } = req.body;
      await xata.db.messages.create({
        author: author,
        message: newMessage,
        img: imgUrl,
      });
      res.end();
    };

    export default handler;
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Imported the getXataClient utility and initialized a new instance
  • Accessed the author, newMessage, and imgUrl from the request’s body
  • Saved the new chat data to the messages table on Xata

Conclusion

With that, we have successfully created a chat application with Cloudinary and Xata. The purpose of this simple is to give a brief overview of the features of these awesome technologies. We can take the project to the next step by validating the form fields during submission and adding user authentication and real-time functionality to the application.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .