DEV Community

Femi-ige Muyiwa for Hackmamba

Posted on • Updated on

How to create real-time video stream comments in Next.js

Appwrite is an open-source Baas (backend as a service), which provides us with all the necessary APIs to build our projects. Appwrite has client libraries such as:

  1. Web SDK (JavaScript)
  2. Flutter SDK (Dart)
  3. Apple SDK
  4. Android SDK

These libraries allow the building of client-based applications and websites. This tutorial demonstrates how to use Appwrite's Web SDK and the Next framework to create a video streaming chat room (comment area).

Aim

We will give a quick run-through of the front end as it is not the focus of the tutorial. This article will demonstrate the following:

  1. Authenticate users using Appwrite's Google auth.
  2. How to track user sessions (get active user and logged out user).
  3. How to create a real-time chat using Appwrite's real-time feature.

Prerequisites

To follow along, you'll need the following:

  • Proficient knowledge of React.
  • Docker installation (recommended) or a DigitalOcean droplet subscription.
  • An Appwrite instance. Check out this article for the setup.

Here is a link to the GitHub repository containing the project. Let's begin!

Getting started

As we said earlier, we can install Appwrite with either Docker or DigitalOcean droplet. We'll use Docker for the tutorial.

Note for Windows users: Before installing Docker, we need either wsl2 or hyper-v on our device. Here is a link to an installation guide for wsl2.

In this section, we will be doing the following:

  1. Building the front-end
  2. Creating a new Appwrite project
  3. Enabling project functionalities

Front-end

We will use Next.js and ChakraUI to build our front-end features. First, let's go over what our front-end is and what it does. To proceed, copy the command below and paste it into your terminal to install Next.js and ChakraUi and ChakraUI icons:

Note: we will use the node package manager throughout this project.

  • Installation for Next.js
npx create-next-app@latest <folder-name> --ts
Enter fullscreen mode Exit fullscreen mode
  • Installation for ChakraUI
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
Enter fullscreen mode Exit fullscreen mode
  • Installation for ChakraUI icons
npm i @chakra-ui/icons
Enter fullscreen mode Exit fullscreen mode

Next, create a components folder within the pages folder containing five typescript files (separate from the default index.tsx and _app.tsx files). We will give a brief explanation below of what each file entails.

Login.tsx

We will call this component the starter pack of the project, as it will contain the functionalities to authenticate users using Google authentication. In this component, we will create a button and set an onClick attribute to a function called googleAuth.

Thus, once a user clicks the button, it triggers the googleAuth function. We will shed more light on the authentication functionalities in the coming sections.


import Head from 'next/head'
import { access } from '../api/appwriteconfig'
import { ChatIcon } from "@chakra-ui/icons"
import { Box, Button, Center, Stack } from "@chakra-ui/react"

export default function Login() {
  return (
    <>
      <Head>
        <title>Login</title>
      </Head>
      <Center h="100vh">
        <Stack align="center" bgColor="gray.600" p={16} rounded="3xl" spacing={12} boxShadow="lg">
          <Box bgColor="blue.500" w="fit-content" p={5} rounded="3xl" boxShadow="md">
            <ChatIcon w="100px" h="100px" color="white" />
          </Box>
          <Button onClick={(e) => googleAuth(e)} boxShadow="md"> Sign in with google </Button>
        </Stack>
      </Center>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

signin

Chat.tsx

This file contains two other components that we will discuss later. It is the entirety of our front-end project, as it handles the rendering of our static video and chat area.

import { Flex} from "@chakra-ui/react"
import ChatBar from "./Chatbar"
import Topbar from "./Topbar"

export default function Chat() {
    return (
        <>
            <Flex >
                <Flex flex={1} h='100vh' w='100%' direction='column'>
                    <Topbar />
                    <iframe width="100%" height="100%" src="https://www.youtube.com/embed/wWgIAphfn2U"></iframe>
                </Flex>
                <ChatBar />
            </Flex>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Topbar.tsx

This component contains a simple heading and avatar. Its purpose is to display the user's name without registering it in the database.

import { Avatar, Flex, Heading } from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
import { access } from '../api/appwriteconfig';
export default function Topbar() {
    const [user, setUser] = useState({
        name:''
    })
    const getuser = async () => {
        const userdata = access && access.get();
        userdata
            .then((res) => setUser(res))
            .catch((err) => {
                console.log(err)
            })
    }
    useEffect(() => {
        getuser();
    }, [])
    return (
        <Flex h='81px' bg='gray.100' w='100%' align='center' p='5px'>
            <Avatar src="" marginEnd={3} />
            {user && (
                <Heading size='lg'>{user.name}</Heading>
            )}
        </Flex>
    )
}
Enter fullscreen mode Exit fullscreen mode

Chatbar.tsx

We use this component file to handle the logout function and chat area (comment). The chat area contains input and a flexbox that displays the messages.

We achieve this display by using the map function to loop over messages and display {message.message} by {message.name}:. For the logout function, we used the icon button component from ChakraUI and set an onClick function to it.

import { ArrowLeftIcon } from '@chakra-ui/icons'
import { Button, Flex, IconButton, Input, Text } from '@chakra-ui/react'
import { useEffect, useState } from "react"
import { useRouter } from 'next/router'
import { access, db } from '../api/appwriteconfig'

export type ChatMessage = {
  name: string
  message: string
}
export default function ChatBar() {
  const [messages, setmessages] = useState<ChatMessage[]>([])

  const ChatArea = () => {
    return (
      <form onSubmit={submitMessage}>
        <Input autoComplete='off' placeholder='Type a comment' type='text' name='message' />
        <Button type='submit' hidden> Submit </Button>
      </form>
    )
  }
  return (
    <Flex w='500px' h='100vh' float='right' borderLeft='1px solid' borderLeftColor='gray.200' direction='column'>
      <Flex padding='5px' h='81px' w='100%' align='center' alignSelf='flex-end' borderBottom='1px solid' borderBottomColor='gray.200'>
        <IconButton onClick={logout} size='sm' isRound icon={<ArrowLeftIcon />} aria-label={''} />
      </Flex>
      <Flex flex={1} direction='column' pt={3} mx={3} overflowX='hidden' sx={{ scrollbarWidth: 'none' }}>
        {messages.map((message) => {
          return (
            <Flex bg='blue.100' w='fit-content' minWidth='50px' margin='5px' borderRadius='lg' p={3}>
              <Text >
                <label>
                  {message.name}:
                </label>
                {message.message}
              </Text>
            </Flex>
          )
        })}
      </Flex>
      <ChatArea />
    </Flex>
  )
}
Enter fullscreen mode Exit fullscreen mode

Homepage.tsx

This component file acts as a housing for the other files; thus, it is the homepage file.

import Chat from './Chat'
function Homepage() {
  return (
    <Chat/>
  )
}
export default Homepage
Enter fullscreen mode Exit fullscreen mode

Now, we can go ahead with creating our Appwrite project.

Creating a new Appwrite project

After installing Appwrite, head to your browser and add the default localhost specified during the installation. Afterward, we can sign up to Appwrite to set up our project configuration.

Once we have set up our Appwrite console, we will create a new project. In this project, create a platform consisting of the web app name and hostname (localhost) and click register. Then, paste the command in the platform section to our terminal to install Appwrite to our node module.

appwrite home

select platform

platform

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

After setting up the platform, we will create a new attribute. To do this, we must create a database > collection > attribute. In our collection settings, we will set a collection level permission (for this tutorial, we'll set it to role:member). Now we're good to go, and our Appwrite console is ready for use.

collection permission

Project functionalities

After installing Appwrite, we will need to initialize its web SDK in our project. To do this, create a new folder, and within that folder, a new typescript file called appwriteconfig.tsx.

Then, copy the code below into the appwriteconfig.tsx file. We now have our web SDK ready for use within our project:

import { Client, Account, Databases } from 'appwrite';
const client = new Client();
const account = new Account(client);
const databases = new Databases(client, 'Comment');
// Init Web SDK
client
    .setEndpoint('http://localhost/v1') // API Endpoint
    .setProject('Chat-app') // project ID
;
export const access = account;
export const db = databases;

Enter fullscreen mode Exit fullscreen mode

Authentication

We want to authenticate the identity of new users, and to achieve that, we will use Google auth services within Appwrite. We have a step-by-step process to follow through with this section:

  • First, we want to set up OAuth 2.0. To do so, head to the API Console.
  • Next, we will go to credentials and click on create credentials. Then, we will select OAuth client ID and fill out the following:
    • Set the application type (For the project, we will select the web application from the drop-down).
    • Then we'll specify our app name, authorized JavaScript URI (localhost:3000), and authorized redirect URI (we can get this by going to our Appwrite console and navigating to Users > Settings. Scroll to Google and turn it on; below, we will copy the callback link from Appwrite and paste it in this section).
    • Finally, click on create.

authopage

auth1page

  • Now, we will get our client ID and secret. Copy these details and paste them into their required field in the Appwrite console. Click update to enable Google.

auth2page

  • Finally, in our Login.tsx file, we will call our googleAuth function and set a preventDefault method (it prevents the page from loading when the function kicks in) and use the createOAuth2Session method to create a session for new users. We have the implementation of the explanation below.
const googleAuth = async (e: any) => {
    e.preventDefault();
    try {
      access.createOAuth2Session(
        'google',
        'http://localhost:3000/component/Homepage',
        'http://localhost:3000/component/Blob'
      );
    } catch (error) {
      console.log(error)
    }
  }
Enter fullscreen mode Exit fullscreen mode

Log out a user

This function redirects to the login page after a user logs out. To do this, we must import useRouter and call the logout function attached to the icon button.

After, we will call the deleteSession on the current user and use the push method to force a redirect back to the index (Login.tsx) page. We have an implementation below:

const router = useRouter()

const logout = async () => {
  access.deleteSession("current")
  alert("Logout successful")
  router.push("/")
}
Enter fullscreen mode Exit fullscreen mode

Creating documents

In our Chatbar.tsx file, we want to grab and populate the input in our database. Thus, we will call the submitMessage function and create two constants (message — capture the value from the input; CurrentUser — get account).

After, we will create a new document with the createDocument method with the parameters (collection id, documents id (use a unique id), data). Below is an implementation of the explanation above:

const submitMessage = async (e: any) => {
  e.preventDefault();
  const message = e.target.message.value;
  const CurrentUser = await access.get()
  await db.createDocument("Chat", 'unique()', {
    name: CurrentUser.name,
    message,
  })
}
Enter fullscreen mode Exit fullscreen mode

Currently, the code above saves our input texts in our documents. Now we want to listen to those messages to grab them as they enter the database.

Listening to messages

We will get started by creating an effect that subscribes and listens to new messages using the subscribe method. For the arguments within the method, we will call an array of channels, followed by a callback function and a payload. After, we will unsubscribe from the channel.

Note: if we do not unsubscribe, we may get our messages multiple times, as the subscribe method continues to listen to the channel provided.

Here is an implementation of the explanation above:

useEffect(() => {
  const unsubscribe = access.client.subscribe(
    ['databases.Comment.collections.Chat.documents'],
    (data) => {
      setmessages((messages) => [...messages, data.payload as ChatMessage]);
    }
  );
  return () => {
    unsubscribe();
  }

}, [])
Enter fullscreen mode Exit fullscreen mode

Grabbing the list of documents

When listening to the messages, we also want to grab the messages immediately, and the list of documents does that effectively. We have the implementation below:

const genRandomKey = async () => {
  const messages = await db.listDocuments('Chat', [], 100, 2);
  setmessages(messages.documents as unknown as ChatMessage[])
};
useEffect(() => {
  genRandomKey();
}, [])
Enter fullscreen mode Exit fullscreen mode

result

Conclusion

This post discussed how to create real-time video stream comments in Next js. We discussed authenticating new users and listening to changes using real-time features in the tutorial.

Resources

Check out Appwrite’s docs on the real-time feature here. Thanks for reading, and happy coding!

Top comments (0)