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:
- Web SDK (JavaScript)
- Flutter SDK (Dart)
- Apple SDK
- 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:
- Authenticate users using Appwrite's Google auth.
- How to track user sessions (get active user and logged out user).
- 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:
- Building the front-end
- Creating a new Appwrite project
- 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
- Installation for ChakraUI
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
- Installation for ChakraUI icons
npm i @chakra-ui/icons
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>
</>
)
}
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>
</>
)
}
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>
)
}
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>
)
}
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
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.
npm install appwrite
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.
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;
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.
- Set the application type (For the project, we will select the
- 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.
- Finally, in our
Login.tsx
file, we will call ourgoogleAuth
function and set apreventDefault
method (it prevents the page from loading when the function kicks in) and use thecreateOAuth2Session
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)
}
}
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("/")
}
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,
})
}
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();
}
}, [])
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();
}, [])
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!