Building robust applications using GQLAlchemy
Tags: python, ogm, memgraph, webdev
Published: 2022-01-31 14:33:28

While building the whole web application, you save the necessary data to the database and load it whenever needed. Fetching the data can be error-prone since there may be that one minor typo that will bug you. In this tutorial, you'll learn how to build a small part of the [Twitch analytics app](https://github.com/memgraph/twitch-analytics-demo) with the help of **GQLAlchemy**, an object graph mapper (OGM) that makes building graph-based apps much easier. 

## Backend implementation

Through this tutorial, you are going to find the most popular game from the Twitch dataset. The [data](https://github.com/memgraph/twitch-analytics-demo/tree/main/backend/import-data) was retrieved from the [Twitch API](https://dev.twitch.tv/docs/api/), and for this tutorial, you will just need the `streamers.csv` file. First, you have to define the necessary data classes in [`models.py`](https://github.com/memgraph/twitch-analytics-demo/blob/main/backend/models.py):

```python
from gqlalchemy import Node, Field, Relationship
from typing import Optional
from app import memgraph
class User(Node):
    name: str = Field(index=True, exists=True, unique=True, db=memgraph)
class Stream(User):
    name: Optional[str] = Field(
        index=True, exists=True, unique=True, db=memgraph, label="User"
    )
    id: str = Field(index=True, exists=True, unique=True, db=memgraph)
    url: Optional[str] = Field()
    followers: Optional[int] = Field()
    createdAt: Optional[str] = Field()
    totalViewCount: Optional[int] = Field()
    description: Optional[str] = Field()
class Game(Node):
    name: str = Field(unique=True, db=memgraph)
```

Next, you have to load the data from the CSV file:

```python
def load_streams(path):
    with open(path) as read_obj:
        csv_reader = reader(read_obj)
        header = next(csv_reader)
        if header != None:
            for row in csv_reader:
                stream = models.Stream(
                    id=row[1],
                    name=row[3],
                    url=row[6],
                    followers=row[7],
                    createdAt=row[10],
                    totalViewCount=row[9],
                    description=row[8],
                ).save(memgraph)
                game = models.Game(name=row[4]).save(memgraph)
                plays_rel = models.Plays(
                    _start_node_id=stream._id, _end_node_id=game._id
                ).save(memgraph)
```

You modeled objects using **GQLAlchemy's** Object Graph Mapper (OGM), which offers schema validation, so you can be sure that the data inside Memgraph will be accurate.
All loading methods can be found in [`twitch_data.py`](https://github.com/memgraph/twitch-analytics-demo/blob/main/backend/twitch_data.py).

After the data is loaded, you can create an API endpoint for GET requests in [`app.py`](https://github.com/memgraph/twitch-analytics-demo/blob/main/backend/app.py) to find out which game is the most popular:

```python
@app.route("/top-games/<num_of_games>", methods=["GET"])
@log_time
def get_top_games(num_of_games):
    """Get top num_of_games games by number of streamers who play them."""
    try:
        results = list(
            Match()
            .node("Stream", variable="s")
            .to("PLAYS")
            .node("Game", variable="g")
            .return_({"g.name": "game_name", "count(s)": "num_of_players"})
            .order_by("num_of_players DESC")
            .limit(num_of_games)
            .execute()
        )
        games_list = list()
        players_list = list()
        for result in results:
            game_name = result["game_name"]
            num_of_players = result["num_of_players"]
            games_list.append(game_name)
            players_list.append(num_of_players)
        games = [{"name": game_name} for game_name in games_list]
        players = [{"players": player_count} for player_count in players_list]
        response = {"games": games, "players": players}
        return Response(
            response=dumps(response), status=200, mimetype="application/json"
        )
    except Exception as e:
        log.info("Fetching top games went wrong.")
        log.info(e)
        return ("", 500)
```

The **query builder** from **GQLAlchemy** was used to avoid writing Cypher queries and to notice if there are any errors before the query is executed in the database. The query above counts how many streamers play each game and then returns results in descending order. This is how the query builder construct looks when executed as a Cypher query in Memgraph:
- **Query builder**
```python
Match()
.node("Stream", variable="s")
.to("PLAYS")
.node("Game", variable="g")
.return_({"g.name": "game_name", "count(s)": "num_of_players"})
.order_by("num_of_players DESC")
.limit(num_of_games)
.execute()
```

- **Cypher query**
```cypher
MATCH (s:Stream)-[:PLAYS]->(g:Game)
RETURN g.name AS game_name, count(s) AS num_of_players
ORDER BY num_of_players DESC LIMIT num_of_games;
```

In this request, you can determine how many top games you want to fetch. The response you'll get in the frontend is JSON with `games` and `players` as keys.

## Frontend implementation

In the end, let's show how you can visualize your results. The [frontend](https://github.com/memgraph/twitch-analytics-demo/tree/main/frontend) is built with the help of Semantic UI React.

You have to create a component in which you're going to fetch the data from the backend. The most important part is the fetching, which you have to call immediately after a component is mounted, that is, in `componentDidMount()` method:

```javascript
fetchData(number) {
    fetch("/top-games/" + number)
        .then((res) => res.json())
        .then(
        (result) => {
            this.setState({
                isLoaded: true,
                games: result.games,
                players: result.players,
            });
        },
        (error) => {
            this.setState({
                isLoaded: true,
                error,
            });
        }
        );
    this.setState({
        numOfGames: number,
        header: "Top " + number + " games",
    });
}
componentDidMount() {
    this.fetchData(this.state.numOfGames);
}
```

Then you can render the results saved in `games` and `players` state variables however you want. If you need inspiration, check out the [`Games.js`](https://github.com/memgraph/twitch-analytics-demo/blob/main/frontend/src/components/Games.js) component.

![twitch-analytics-demo](https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/main/images/app_2.png)

## Conclusion

Creating classes representing nodes and relationships in Python offers you more control of your data. GQLAlchemy enables you to easily communicate with the graph database using the technology stack you're already familiar with. If you have questions about GQLAlchemy's OGM or query builder, visit our [Discord](https://discord.gg/memgraph) server and drop us a message. You can also share any project you come up with!