useMindmap
Tags: appwritehack, react, webdev, typescript
Published: 2022-05-10 10:23:43

### Overview of My Submission

`useMindmap` is a mind mapping application backed by Appwrite. It leverages Users/Teams, Database, Realtime, and Functions to provide a basic mind mapping service for yourself or a team.

Live: https://usemindmap.app

![View of the useMindmap dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6vg0d220kagz1h4yzha.png)

![View of the mindmap flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60qt5rk7apsj4r0zs5ny.png)
 
{% embed https://youtu.be/KdPJ4iKJuDw %}

* Personal and Team workspaces for your mind maps
* **Collaborative mind mapping powered by Appwrite Realtime**
* Mind mapping functionality built on top of React Flow
* Sign up/sign in, forgot password, and team invites

I recently came across the React Flow project and wanted to try building a team-oriented mind mapping app with it, but didn't quite have the backend experience necessary for the _collaborative_ feature the app needed. So when I saw that Appwrite offered Realtime features that could subscribe to database events, I knew I had a potential killer combination on my hands. The hackathon gave me the perfect excuse and motivation to make it a reality, however, I was wholly unprepared for how truly excellent Appwrite is. Let's dive in.

#### Building useMindmap

The first step was to deploy Appwrite. I've used Digital Ocean for some time and I love their [single-click app marketplace](https://marketplace.digitalocean.com). This got me a cheap droplet running Appwrite in about 5min. Using an existing domain for testing purposes I set up a subdomain for the Appwrite instance, played around with the admin console and thought _"Hell yeah, this is awesome!"_

I created a Web project, configured environment variables, and did all the housekeeping necessary to prepare for full-scale development. After two days getting acquainted with Appwrite I was ready to build.

##### FRONTEND

I started the frontend with a simple React project template (webpack, babel, eslint, typescript) and blocked out the page routes I would need:

* Home (`/`)
* Auth ( `/sign-in`, `/sign-up`, etc)
* Dashboard (`/~`, `/~/profile`, `/~/teams`, etc)
* Mindmap (`/~/maps`)

Using inspiration from [Chakra UI Pro](https://pro.chakra-ui.com) and [Tailwind UI](https://tailwindui.com) I then created the sign-up/sign-in pages and integrated with the Appwrite SDK to create a user and a logged in session.

The Dashboard followed with the sidebar and content:

* Logged in user details
* Main "activity" view
* Profile and Teams view
* "Workspaces" list and view

![View of the user's personal workspace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/be3ywl8u9xarvavdns6g.png)

From some tinkering around with Appwrite's Database and SDK, I settled on making each workspace a Collection and each Mindmap a Document with attributes:

* name: string
* description: string
* nodes: string[]
* edges: string[]
* tags: string[]

After a couple weeks fleshing out the UI and getting intimate with the Docs and SDK, I was finally tackling the MIIINDMAAAP (\**spooky music*\*)

When a user creates a mindmap for a given workspace, a document is created in the associated collection and the user is launched into the `/~/maps/:id` path. Once inside the Mindmap view, the React Flow instance is used to create nodes, moved them around, save a label, attach edges, and delete them.

React Flow represents its nodes and edges as objects with attributes like `id`, `position`, `width`, `height`, `source`, `target`, there are many many more properties but these ones are the important bits so they can be saved/restored and propagated to other connected clients.

Each one of these events triggers a Function execution with a payload of the new value (a node move event sends the updated `position`, a label update sends a new `data` object, etc).

From this point, it's up to Appwrite Functions to resolve the mutation on the mindmap document. Onward, to the Backend!

##### BACKEND

I wanted the concept of a group of mindmaps to be something the user or team doesn't even need to manage. To support this, using Appwrite Functions, every `user.create` or `teams.create` system event creates a new collection only that user/team has access to. In effect, this produces a "scoped workspace" of sorts for the user/team to call their own and is entirely automatic.

(As a side note, I also use a Function to generate a `gravatar` URL for each user upon registration)

When a user opens a mindmap, the client fetches the initial state of the document while also subscribing to document changes with the Realtime endpoint. Realtime changes received by the client are then merged to the React Query cache to keep them in sync (honestly, React Query isn't even needed to push Realtime data from the callback response to React Flow, but it's just so damn easy to use for handling server state that I can't _not_ use it)... moving on.

With clients listening to documents for changes, what's left is for Appwrite Functions to resolve updates to nodes and edges. To do this, I created two Functions to handle `nodes` and `edges`, whose responsibilities are:

1. Accept the incoming payload from a HTTP event
2. Pull the most up-to-date document from Database
3. Insert/merge/remove the changed attribute into/from the original attribute
4. Save the new document attributes back to Database
5. Exit gracefully stage right, grab a coffee

Given the schema of the mindmap document (where nodes and edges are arrays of strings), each incoming node and edge object gets stringified before written to the Database and parsed when read by the client. The challenge is that some events may conflict if mutating the same attributes before an existing mutation has been persisted to the document. In these cases, it's simply whichever event arrives last that is most likely to be written. While a true transactional database would help alleviate this issue it's not too much of a concern for the relatively slow pace of events in a mindmap.

This was a conscious choice with an acceptable level of risk for the scale of the app, compared to the added complexity of using a Collection per Mindmap and treating nodes and edges as individual documents. That approach would involve mapping each React Flow object property to a document attribute, increase Database operations when setting up a new mindmap and reading a list of mindmaps from a "workspace", and also when cleaning up (e.g. user deletes their account/team and all associated mindmap documents). Not to mention the query complexity, and creating document indexes for improved throughput performance.

#### WRAPPING UP

This all seems like an oversimplification, and while it's rather straightforward once the steps are laid out like this, I did get stuck inside my own head for about a week as I considered all sorts of funky solutions like [CRDT](https://crdt.tech/)s, custom WebSocket servers with [Yjs](https://yjs.dev/), even using a separate service like [Pusher](https://pusher.com/)! Ultimately, Appwrite provided the right tools I needed to get useMindmap up and running as envisioned without needing to go outside the box. Kudos to the Appwrite team, I'm excited to see what else they have planned.

I'd like to shout out the invaluable support offered by the Appwrite Team in the Office Hours channel on Discord, particularly `@Brandon` and `@VincentGe`, you guys rule.

### Submission Category: 

Web2 Wizards

### Link to Code

Proudly open source on GitHub: https://github.com/benweier/use-mind-map

### Additional Resources / Info

#### Tech Stack

* Appwrite (duh)
* React
* Digital Ocean (1x droplet + 1x static site app)

Key Dependencies:

* [React Router](https://reactrouter.com)
* [React Flow](http://reactflow.dev)
* [React Query](https://react-query.tanstack.com)
* [React Hook Form](https://react-hook-form.com)
* [Chakra UI](https://chakra-ui.com)

#### Future Plans

Obviously this is a pretty quick'n'dirty mind mapper, but I loved the experience of combining Appwrite with React Flow that I plan to take this further and develop it into a fully-fledged app.

The mindmap experience right now is basic and rough, and there's not a lot of expressive features to make a mindmap your own. Having options like colours and shapes for the nodes at least, as well as making editing seamless - like opening the node edit panel above the selected node, shortcut keys to add a pre-linked node, an automatic layout engine with `elkjs` - would all go a long way.