Building Instant Messaging App With Flask, Socketio, and Fauna
Tags: python, websockets, fauna, flask
Published: 2021-06-10 14:42:03

Whatsapp, Messenger, Telegram, and other instant messaging apps make use of WebSockets. According to Wikipedia, WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection.

A duplex communication system is a [point-to-point](https://en.wikipedia.org/wiki/Point-to-point_(telecommunications)) system composed of two or more connected parties or devices that can communicate in both directions. In a full-duplex channel, both parties communicate simultaneously. Instant messaging applications implement the full-duplex concept via protocols like WebSockets. 

In this post, we will be exploring how to build a serverless instant messaging application.

# What Is a Serverless Application?

Serverless architecture, also known as function as a service, FaaS, is a software design pattern where applications are hosted by a third-party service, eliminating the developer's need for server software and hardware management. (cited from <https://www.twilio.com/docs/glossary/what-is-serverless-architecture>)

# Advantages of Using a Serverless Database

Serverless databases reduce the effort put into managing the database resources. It allows you to focus on other essential parts of your application. Some other advantages of using a serverless database include: 


* Reduction in operational database cost
* Improved database security 
* Improved scalability
* Realtime database access 
* Quicker deployment time

# Introduction to Fauna

Fauna is a serverless database. Fauna is a document database that offers GraphQL and Fauna Query Language (FQL) at its nucleus. You can store collections, indexes, and other databases ( multi-tenancy). Fauna can handle multiple data types. Fauna doesn't have strict schema requirements and has top-notch support for data relationships. You can check it out at: <https://docs.fauna.com/fauna/current/>

# Setting Up your Fauna Database

First, you need to create an account with Fauna. You can do that at: <https://dashboard.fauna.com/accounts/register> 

## Creating a New Database

You need to create a database your Flask Application will interact with. First, click on the `New Database` button. It should look like the one in the image below.

![create new fauna database screen](https://lh4.googleusercontent.com/OC4ojaKQmaP96fD8Q9feg6R4j4iO1W7xmDtt6onXImtkXSPPWxKNNfYTfU4rnAPZKQxxEl-2QNyi3befAEcDmJb3fsjRyl1gbFaM3FpnT49h4idbX1Mu58Ta7RRkUwlu9Q)

Then, enter the database name. 

![Create new fauna database form](https://lh4.googleusercontent.com/jgbps3RhsFkq2rxyy8umAMu8XHywx3HIEQaqBgD2JU1ppsWAVzTQto-amMWKZqeRN_oKtKycJFyvkXBd5TdPPRZoAIOd9hUcmSFGZ_7_Cn2cbjZhwek10cSEpPzXOzLWsQ)

Click on `SAVE` to create the database. You will see this:

![fauna dashboard](https://lh5.googleusercontent.com/sPIw-HO0b4Fif0aPEVjJwjyDW3rBvx1OTMOvyBO06lk35aPLF3AzkbBkOG-fpPV15M0implvtvnyrzlD55xt2SxTSgyWjVUIZhBjoWhn9bl5EtYpe-zi681gPgzEdupl8g)

Now, you have created a new database.

## Creating a Fauna Collection

Click on the `NEW COLLECTION` button to create a collection. A collection is similar to tables in SQL that contain data with similar characteristics. Fauna organizes its data into collections and uses indexes to browse through it. Data in a collection is organized as documents. 

Next, you fill out the details of your collection.

![Create fauna collection screen](https://lh6.googleusercontent.com/2L2413hPFvqV8pCVoUVJgEHv166pxtCLRJHGlDJep9NKVq4ondydDZTGTV7poKa5TFrAwkJbKf6gLX2QeSBqTiNkPgnAjjUSN2spC_jDhyIpKBcHiFSAaVhtstrG24bKyA)

The collection name for this example will be `users`. You can leave the other fields with their default values. Click on `SAVE`. You will see something similar to this:

![fauna database collection dashboard](https://lh4.googleusercontent.com/owHNaKa4OfiyStfSGH29OraHySJcOyuyKnUHopRIax_czrZGr_ydEChwBFDLaFofYyXdyFuWpN28xBAG8dzK8jWpHtefkrZNbCLIQvMPCShozHrXhyECnWFzKxVAhZdfbw)

Now, we have to create indexes for our collection.

## Creating a Fauna Index

Indexes allow us to browse through our database. Navigate to Indexes and click on `NEW INDEX.` You have to fill out the details of the index.

 

![Create new fauna index screen](https://lh3.googleusercontent.com/z27QmWQrDjA_IFbrkl0-eG-SI7tiElMmjmKqd7ieC1N_PFqXW-a9ciXCPG_eYZ8upvqi2vAHqAoH_UkiwkcGkCDjPoXjdDvLx12-8hJ9MfbLxxRjWroDzsITq74u7T4ClA)

Fill out the id in the Terms field. Terms specify the data the index is allowed to browse. Click the `SAVE` button.

You have now created a Fauna index.

## Creating Your Database API Key

We need to create an API key to allow us to communicate with our database from Python securely. To generate the key, navigate to the Security tab on the left sidebar.

![Create fauna database connection key](https://lh5.googleusercontent.com/9uZc3UVGJ4gt6tkIgR977vE4ywh1H_PvhJqvwqoKdq2XsDtgVSE4Ovs1SsBrBq2uvfAIcYst9OmoKf4uJXlYK6l4eLnmSORyq2hwdtH_wkRYGbx-CKN2Oks9dDnCQQ2xZQ)

Generate the new key by clicking on the `NEW KEY` button.

You will be asked for the database to associate the key with, the key's role, and the optional name for the API key. Provide that. Then, click on the `SAVE` button.

![display fauna database connection key](https://lh3.googleusercontent.com/ppqOEJCm73rlBksHnOIheXBPAD6FMmLU97yl_xN1PI3q9hMFRLlje-x7rLTcpdcgirQwNB1Or-704bcCuNmaA7pHWxekhA1YsfSpOqXrQQgTmWrt2VgrBi6vK2V6aZh1Qw)

You will now see your API key (hidden here). Remember always to keep your key private. 

Copy and store the keys where you can easily retrieve them.

## Wiring it Up to Python

 ```bash

pip install faunadb

```

After installing, run this sample code provided by Fauna: <https://docs.fauna.com/fauna/current/drivers/python.html>

```python

from faunadb import query as q

from faunadb.objects import Ref

from faunadb.client import FaunaClient

client = FaunaClient(secret="your-secret-here")

indexes = client.query(q.paginate(q.indexes()))

print(indexes)

```

If this runs successfully, it means you are done setting up Fauna.

# Building the Application

We have now set up the serverless database for our application. We can get our hands dirty with some good stuff. You can clone the project's repository (`git clone <https://github.com/Bamimore-Tomi/fauna-chat.git>`). Go through the Readme.md file for instructions on how to use the web application.

## Application Structure

The structure of the repository should look like this.
![fauna-chat application structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofkncyniuhbp1f1r3oa6.PNG)
### 

The `.env` was intentionally ignored. It contains app secrets.

## Installing Project Requirements

For starters, you have to install the requirements using pip. Running this command in the project's directory will handle that for you.

```bash

pip install -r requirements.txt

```

Simply running `python main.py` will get the server up and running. Let's run through the routes to see how they work.

# Setting up Our Flask Routes

{% gist https://gist.github.com/Bamimore-Tomi/f5b1632aa41d73d26ff1671e7ae55061 file=fauna-chat-main-01.py %}

We initialize the Fauna client using `client=FaunaClient(secret=os.getenv("FAUNA_KEY"))`. The Secret key is defined in the `.env` file. All requests to Fauna will be from the `client` object.

```python

# main.py

app = Flask(__name__, template_folder='templates')

app.config["SECRET_KEY"] = "your-secret-key"

socketio = SocketIO(app)

```

The main Flask app is instantiated with the `app` variable. In the `socketio` variable, we use `SocketIO` to enable bi-directional communications between the client and the server. 

The following function is a custom login decorator. We will use it to decorate protected routes later in the file. If the user session is not set, they will be redirected to the login page. 

```python

def login_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if "user" not in session:
            return redirect(url_for("login"))
        return f(*args, **kwargs)

    return decorated

```

## The Register Route

The `register` view receives data from the HTML form and saves it to the database. It is good practice to always hash your passwords before storing them in your database. We used the `hashlib` library for that function. If this is successful, a new user and chat document is created.

## The Login Route

The `login` view validates the credentials submitted by users. If this is successful, a session is set for the user. It is essential to understand how to query Fauna using the indexes you created.

```python

user = client.query(q.get(q.match(q.index("user_index"), email)))

```

When we created the `user_index`, we added the **Term** **email** (the terms field specifies which document fields can be searched ). Now I am querying the users' collection using the **user_index** created earlier.

## Chat Route

{% gist https://gist.github.com/Bamimore-Tomi/f5b1632aa41d73d26ff1671e7ae55061 file=fauna-chat-main-02.py %}

The chat route handles several things. First, it fetches all ids in the user chats list.

```python
chat_list = client.query(q.get(q.match(q.index("chat_index"), session["user"]["id"])))[
    "data"
]["chat_list"]
```

We can also liken the **chat_list** to a **friend list**. A number of try and except blocks were used to create alternative action in case a query fails. For example:

```python
try:
    chat_list = client.query(
        q.get(q.match(q.index("chat_index"), session["user"]["id"]))
    )["data"]["chat_list"]
except:
    chat_list = []
```

If no one has been added to a user's chat list, we return an empty list.

At the function's beginning, the `room_id` variable is set from the optional `rid` url argument. The room id is a unique identifier generated for every chat room. According to [socketIO](https://socket.io/), a room is an arbitrary channel that sockets can join and leave. It can be used to broadcast events to a subset of clients. In this case, a "subset of clients" consists of just two users.

## New-Chat Route

This route enables a user to add other users to their chat list.

{% gist https://gist.github.com/Bamimore-Tomi/f5b1632aa41d73d26ff1671e7ae55061 file=fauna-chat-main-03.py %}

It confirms that the users are not trying to add themselves, 

```python
if new_chat == session["user"]["email"]:
    return redirect(url_for("chat"))
```

a user that does not exist 

```python
try:
    # If user tries to add a chat that has not registered, do nothing
    new_chat_id = client.query(q.get(q.match(q.index("user_index"), new_chat)))
except:
    return redirect(url_for("chat"))
```

A user that has already been added `if new_chat_id["ref"].id() not in chat_list:`. 

Another vital operation that this route performs is generating the room id.

```python

room_id = str(int(new_chat_id["ref"].id()) + int(user_id))[-4:]

```

It adds the user id of both users and uses the last four digits as the room id for their chats.

# Flask-SocketIO Events

We have seen the regular Flask routes in the application. We will not explore the Flask-Socketio Events in the application.

{% gist https://gist.github.com/Bamimore-Tomi/f5b1632aa41d73d26ff1671e7ae55061 file=fauna-chat-main-04.py %}

We have two events here. In an actual production application, you will likely have more events. 

## Join-Chat Event

The first `join_private_chat` event is used for adding a user to a specified room. When a client connects to a chat, the URL path will look like this: `https://fauna-chat.herokuapp.com/chat/?rid=0488`

The SocketIO client uses the **rid** to know who the user wants to chat with. The client now requests the server to add the user to that room.

```javascript

var socket = io.connect('http://' + document.domain + ':' + location.port + '/?rid=' + {{ room_id }} );

socket.on( 'connect', function() {

 socket.emit('join-chat', {

 rid: '{{ room_id }}'

 } )

 } )

```

Once this process is completed, the server now knows who to send a specific message to.

## Outgoing Event

The `chatting_event` function is used when a user sends a new message. It simply saves the message to the database.

```python
messages = client.query(q.get(q.match(q.index("message_index"), room_id)))

conversation = messages["data"]["conversation"]

conversation.append(
    {
        "timestamp": timestamp,
        "sender_username": sender_username,
        "sender_id": sender_id,
        "message": message,
    }
)

client.query(
    q.update(
        q.ref(q.collection("messages"), messages["ref"].id()),
        {"data": {"conversation": conversation}},
    )
)

```

And broadcasts the message to the specified user.

```python
socketio.emit(
    "message",
    json,
    room=room_id,
    include_self=False,
)
```

# Touring the Application
![fauna-chat register page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy3bmoz6qqplvbkizhrw.PNG)

![fauna-chat login page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82a9h2qn3ghbcll56sk9.PNG)
![fauna-chat chat room page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u8fcmcu6qikwrgd6w7ki.png)
# Conclusion

In this article, we built an Instant Messaging app with Fauna's serverless database and Python. We learned how simple it is to incorporate Fauna into a Python application and had the opportunity to test out some of its most essential features and functions.

Please contact me via Twitter: [@__forthecode](https://twitter.com/forthecode__) if you have any questions.