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:
- Knowledge of React and Next.js
- A Cloudinary account
- A Xata account
- Understanding of Chakra UI is advantageous but not required
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.
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
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
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.
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.
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
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.
Once in the browser, give the API a name and click the Create API key button.
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.
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 } };
}
Here, we did the following:
- Imported the
getXataClient
utility and initialized a new instance - Queried the
messages
table and fetched all records withgetServerSideProps
- 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>
);
}
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>
);
}
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 theutils
directory - Passed
ShowImageUploadWidget
to the Upload Avatar button’sonClick
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;
JavaScript
Let’s breakdown the snippet above:
-
ShowImageUploadWidget
calls thecreateUploadWidget
that exists in thecloudinary
object -
createUploadWidget
takes in ourcloudname
as part of its config -
ShowImageUploadWidget
accepts thesetImgUrl
function as an argument. If an avatar is uploaded successfully, theimgUrl
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;
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>
);
}
Here, we did the following:
- Created a
handleSubmit
function that takes the form data and passes it on theadd-messages
route. Theadd-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’sonSubmit
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;
Here, we did the following:
- Imported the
getXataClient
utility and initialized a new instance - Accessed the
author
,newMessage
, andimgUrl
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.