How to build an end-to-end encrypted chat app in Next.js: Messages and encryption
Tags: javascript, appwrite, nextjs, encryption
Published: 2022-08-30 16:46:49

Encryption ensures data security through scrambling so that only authorized people can access and decode the information or data.

## What we will be building 

This post discusses using the Appwrite database feature to build a chat app. In this article, we’ll ensure data security using the cryptr library for encrypting and decrypting strings.

**GitHub URL** 

https://github.com/Iheanacho-ai/chat-app-nextjs

## Prerequisites 

This article is part 2 of the two-part series that describes how to build an end-to-end encrypted chat using Appwrite in a Next.js app. It is crucial to start with the first part to get the most out of this article. Check out the first part of the article [here](https://dev.to/hackmamba/how-to-build-an-end-to-end-encrypted-chat-app-in-nextjs-setup-and-auth-5eoj).

## Installing cryptr

crypt is a simple **aes-256-gcm** module for encrypting and decrypting values of UTF-8 strings.

To install crypt, run this terminal command in our project directory.

```bash
    npm install cryptr
```
## Creating collection and attributes

On the left side of the Appwrite [Console](http://localhost/console) dashboard, click on the **Database tab**.

![Appwrite Console Dashboard](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658675116766_Screenshot+470.png)


Click on the **Add Database** button to create a new database. Creating a new database will lead us to the **Collection** page.

Next, we’ll create a collection in our database tab by clicking the **Add Collection** button. This action redirects us to a **Permissions** page.



![Appwrite Console](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658675146058_Screenshot+471.png)


At the **Collection Level,** we want to assign a **Read Access** and **Write Access** with a **role:all** value. We can customize these roles later to specify who has access to read or write to our database.


![Appwrite Console](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658675186164_Screenshot+474.png)


On the right side of the **Permissions** page, copy the **Collection ID**, which we’ll need to perform operations on the collection’s documents.

Next, go to the attributes tab to create the properties we want a document to have. 

![Appwrite Console](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658675211331_Screenshot+475.png)


Let’s create a string attribute of **message** with a size of 256 bits.


![Appwrite Console](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658675259429_Screenshot+477.png)



![Appwrite Console](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658675402025_Screenshot+478.png)

## Adding Chat app Interaction with Database

In the `chat.jsx` file inside `pages` folder, import the useState hook for handling states, Appwrite’s `client` instance, and Appwrite’s `Databases` method.

```js
    import {  useState } from 'react';
    import { client} from '../init' 
    import { Databases } from 'appwrite';
```
Next, create two state variables:
-  A `messages` state variable to hold the messages that a user is about to send.
-  A `databaseMessages` state variable to hold the messages retrieved from the database.

```jsx
    const [message, setMessages] = useState("");
    const [databaseMessages, setDatabaseMessages] = useState(["hey"])
```
 
We then create a `databases` instance using the Appwrite `Databases` method. This `Databases` method receives the client and the databaseID as parameters.

```jsx
    const databases = new Databases(client, 'DatabaseID');
```
Next, we create a `listMessages` function and a `sendMessage`  function.

```jsx
    const listMessages = async () => {
      const promise = await databases.listDocuments('CollectionID');
      promise.documents.map((document) => setDatabaseMessages(prevArray => [...prevArray, document.message]))
    }
```

The `listMessages` in the code block above does the following:
- Lists all the documents in a collection using Appwrite’s `listDocuments` method. This `listDocuments` method receives a collectionID as a parameter.
- Updates the `databaseMessages` state variable with the messages saved in the documents.

```jsx
    const sendMessage = async () => {
      try {
        await databases.createDocument('CollectionID', 'unique()', {
          "message": message
        });
        alert('message sent')
        setMessages("")
        listMessages()
       }catch (error) {
        console.log(error)
      }
    }
```

The `sendMessage` function above does the following:

- Creates a new document using Appwrite’s `createDocument()` function. This `createDocument()` function receives a collection ID, a unique() string, and attribute values as parameters.
- Alerts us when we have successfully saved the message.
- Clears out the `message` variable and calls the `listMessages()` function.
- Logs any errors encountered to the console.

Next, we check whether the `databaseMessages` array is empty, then we loop through the data in the `databaseMessages` array and render the messages in our chat app.

```jsx
    <div className='messages'>
       {
        databaseMessages ? (
          <div className="message-container">
             {
               databaseMessages.map((databaseMessage)=> (
                <div className="user-message">{databaseMessage}</div>
              ))
            } 
          </div>
        ) : null
      } 
    </div>
```
Next, we’ll pass the `message` variable as a value to our input field and our `sendMessage` function to the `onClick` event listener on our **send** button.

```jsx
    <div className='input-area'>
      <input type="text" className='message-input' value={message} onChange={(e) => setMessages(e.target.value)}/>
      <button className='send' type='button' onClick={sendMessage}>send</button>
    </div>
```
After we are done with this section, this is how our `chat.jsx` file looks.

```jsx
    import {  useState } from 'react';
    import { client} from '../init' 
    import { Databases } from 'appwrite';
    
    const Chat = () => {
      const [message, setMessages] = useState("");
      const [databaseMessages, setDatabaseMessages] = useState(["hey"])
      
      const databases = new Databases(client, 'DatabaseID');
      
      const listMessages = async () => {
        const promise = await databases.listDocuments('CollectionID');
        promise.documents.map((document) => setDatabaseMessages(prevArray => [...prevArray, document.message]))
      }
    
      const sendMessage = async () => {
        try {
          await databases.createDocument('CollectionID', 'unique()', {
            "message": message
          });
          alert('message sent')
          setMessages("")
          listMessages()
        } catch (error) {
          console.log(error)
        }
      }
    
      return (
        <div className='chat'>
          <div className='user-chat'>
            <div className="user-chat-header">USER</div>
            <div className='messages'>
              {
                databaseMessages.map((databaseMessage)=> (
                  <div className="user-message">{databaseMessage}</div>
                ))
              }
            </div>
            <div className='input-area'>
              <input type="text" className='message-input' value={message} onChange={(e) => setMessages(e.target.value)}/>
              <button className='send' type='button' onClick={sendMessage}>send</button>
            </div>
          </div>
        </div>
      ) 
    };
    export default Chat;
```
Here is how our chat app looks.

![Chat App](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658683042583_chat.gif)

## Encryption

Cryptr requires a secret key to encrypt or decrypt a string. At the root of our project, we create a `.env.local` file that will contain our secret key.

```
    ## .env.local
    
    NEXT_PUBLIC_KEY = "********"
```
Using the "NEXT_PUBLIC" prefix when storing our secret key allows the environment variable to be available in our component.

Next, we import the **crypt** library in our `chat.jsx` file.

```jsx
    import {  useEffect, useState } from 'react';
    import { client} from '../init' 
    import { Databases } from 'appwrite';
    
    const Cryptr = require('cryptr');
```
We then create a cryptr instance to encrypt and decrypt our string using our secret key.


  ```jsx  
    import {  useEffect, useState } from 'react';
    import { client} from '../init' 
    import { Databases } from 'appwrite';
    const Cryptr = require('cryptr');
    
    const Chat = () => {
      ...  
      const cryptr = new Cryptr(process.env.NEXT_PUBLIC_KEY);
      return (
        ...
       ) 
    };
```
The `sendMessages` function in our `chat.jsx` file will be responsible for encrypting the data. The crypt library lets us use the `encrypt` function to encrypt strings in our application.

```jsx
    const sendMessage = async () => {
      // encrypt the string in our message state variable
      const encryptedMessage = cryptr.encrypt(message)
       try {
        await databases.createDocument('62dc54f155f11d4c38cb', 'unique()', {
        // stores the encrypted message instead of the original message
           "message": encryptedMessage
         });
         alert('message sent')
         setMessages("")
         listMessages()
       } catch (error) {
        console.log(error)
      }
    }
```
The `sendMessage` function encrypts the data in the `message` state variable and then stores the encrypted data on our Appwrite database.


![Chat App](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658683291949_Screenshot+493.png)


The "hey" message gets encrypted and stored in our database as a bunch of numbers.


![Appwrite Databse](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658683559604_Screenshot+494.png)


Next, we’ll retrieve the encrypted data from our database and decrypt it to get the original message.

In the `listMessages` function, we’ll now decrypt the message we obtained from the Appwrite database.

```jsx
    const listMessages = async () => {
      const promise = await databases.listDocuments('62dc54f155f11d4c38cb');
      setDatabaseMessages([])
      promise.documents.map((document) =>{ 
      // map through the documents in the collection and decrypt each message
        const decryptedMessage = cryptr.decrypt(document.message)
        setDatabaseMessages(prevArray => [...prevArray, decryptedMessage])
      }
       )
    }
```
The `listMessages` function cleans out the `databaseMessages` array before looping and decrypting the messages in the document.

![Appwrite Database Console](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658683682702_Screenshot+496.png)


Here is how our chat app should look.

![Appwrite Database](https://paper-attachments.dropbox.com/s_0A370A7EFCD5DF4D65BBF281C6B87BD2E7553044EC7A1A8C0348D2875C691889_1658684220094_chat.gif)

## Conclusion

This article discussed achieving an end-to-end encrypted chat with cryptr and Appwrite.

## Resources
- [Create a local Appwrite instance in 3 steps](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba)
- [Database API](https://appwrite.io/docs/client/database)
- [Installing cryptr](https://www.npmjs.com/package/cryptr)