Vue3 + Apollo + Realm = <3 (Realm Tutorial Pt. 4)
Tags: vue, mongodb, graphql
Published: 2021-09-16 03:28:28

Before we visualize our shipwrecks collection, we need to learn a few things about Vue, Open Street Maps, and how to call our Realm backend. 

For simplicity, we are going to define an anonymous authentication method, aka anyone can make a get call. We are going to use [Vue Apollo](https://v4.apollo.vuejs.org/). This is an easy qay to integrate GrapQL + reactivity into your Vue app while mostly abstracting away the complexity. 

We are also going to use a javascript library to visualize our data. I have found [OpenLayers](https://openlayers.org/), and more specifically the [Vue 3 Flavor](https://github.com/MelihAltintas/vue3-openlayers), to be super simple to work with. Finally, let's us look at parts one through three.

### We have learned about the powers MongoDB Realm
{% link https://dev.to/nicholasoxford/an-introduction-to-mongodb-realm-part-1-1ea0 %}
### Explored our GeoSpatial Collection
{% link https://dev.to/nicholasoxford/understanding-your-mongodb-collection-shipwrecks-realm-tutorial-part-2-5g9k %}
### And cleaned up our data to create and pass a GraphQL Schema
{% link https://dev.to/nicholasoxford/preparing-mongodb-data-for-graphql-realm-tutorial-part-3-31m7 %}

Now it's time to create the frontend to display our data. Even if you have never coded something in a javascript framework, you could still do this. If I am being honest, I think Vue2 is more beginner-friendly than Vue3. The thing is - Vue2 is a bunch of boilerplate. So much scaffolding but it's easy to organize your code. 

Vue3, to me, feels like a mature beast. It is still approachable, but you are losing some of the bowling bumpers that Vue2 had. The tradeoff is there is way less code, and with `<script setup>` you can make a reactive front end in just a few lines. You are going to see see things like `ref()` and `computed(() => {})`. While you can read about these foreign boys in the docs, using them might be the quickest way to learn *why* and **how** these properties are used. 

{% github https://github.com/nicholasoxford/realmVueTemplate %}

Let's start out by downloading my Github repo. It is a starting point that has Apollo, OpenLayers, and Vue3 preconfigured. I will also add a link at the end to a completed repo for you to test out. I highly recommend trying to follow the rest of the tutorial.


The first file we are going to look at is `main.js`. It is where we mount and configure packages like openLayersMap and Apollo. If you think about it, we want to be able to call our GraphQL data anywhere in the app. To do this we have to globally configure Realm on the client-side and tell Vue our URL.

```js
const id = "test-iuege";
const config = {
  id,
};
const appRealm = new Realm.App(config);// Gets a valid Realm user access token to authenticate requests

// HTTP connection to the API
const httpLink = createHttpLink({
  // You should use an absolute URL here
  uri: 'https://realm.mongodb.com/api/client/v2.0/app/test-iuege/graphql',
  fetch: async (uri, options) => {
    const accessToken = await getValidAccessToken();
    options.headers.Authorization = `Bearer ${accessToken}`;
    return fetch(uri, options);
  },
})

async function getValidAccessToken() {
  // Guarantee that there's a logged in user with a valid access token
  console.log(appRealm)
  if (!appRealm.currentUser) {
    // If no user is logged in, log in an anonymous user. The logged in user will have a valid
    // access token.
    await appRealm.logIn(Realm.Credentials.anonymous());
  } else {
    // An already logged in user's access token might be stale. To guarantee that the token is
    // valid, we refresh the user's custom data which also refreshes their access token.
    await appRealm.currentUser.refreshCustomData();
  }
  return appRealm.currentUser.accessToken
}
```
Replace `const id = "test-iuege";` with your RealmID:

![Screen Shot 2021-09-13 at 9.25.37 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5cymw6vijlmg9dp89r3.png)
 
Next click the GraphQL tab in the Mongo UI and copy and paste the URL on the right hand of the page. In the code replace the text of the variable `URI` with what is in your clipboard.

Finally, click the authentication tab, click the edit button next to `Allow users to log in anonymously`, and switch it to on. Remember you have to deploy your changes when using Realm. If you look at the `main.js` again, you see we call appRealm.login() with anonymous credentials. I am sure you could hand-code that, but these packages make it so easy to configure things like this on the client-side. 

Before we change any frontend code, let us go ahead run `npm run dev` in the terminal. If you go to your link, most likely [http://localhost:3000/](http://localhost:3000/), you will see the standard Vue getting started page. I think it is beneficial to have our app running so we can see the changes in real-time. This is known as Hot Module Reloading, or HMR. One last time, look back `main.js` and you will see `import OpenLayersMap from 'vue3-openlayers'`. This allows us to utilize OpenLayers components anywhere in our app. 

Looking at [Open Layer's Vue 3 Website](https://vue3openlayers.netlify.app/componentsguide/geometries/geommultipoint/#usage), we see a list of components we can add to our template. With the fact I want to visualize more than one shipwreck, I chose the `ol-geom-multi-point` component. Looking at this page, we see the `<template>` code which highest level component is `ol-map`. I would recommend clicking the ol-map component page, and exploring the rest of the site. In my opinion, you can learn enough from just ol-geom-multi-point page, but it's helpful to see how other examples utilize the code. 

On the site, below the template code, we see the example`script` code section. Because we are going to use the composition API, our final result might look a little different. Let's go ahead and jump back to VsCode (I don't want to hear it) and navigate to `/components/HelloWorld.vue`. Here you will need to delete or comment out everything in the `<template>` section.

Next, take everything from OpenLayers and replace what was just removed. Inside our updated `<template>` code, the component we will worry most about is:
```js
<ol-geom-multi-point/>
```
In the code you copied over, you will see `:coordinates=` is equal to an array of long, lat coordinates, or an array of arrays! We are going to set coordinates to a varaible later. If you scrool, you will see in the script section I have the code you need commented out. Go ahead and delete or comment the code that is already there. Notice how there is no return statement like there was in the online example. This is beauty of `<script setup>`... Thank you Evan You!

The other difference is that my center variable is different. This is because the online example shows some spots in the middle of china, about as far as you can get from shipwrecks. I put the center point to Port-au-Prince, which has a bunch of shipwrecks. Maybe in part 5 we could make it where, as we move the map, it queries our database for the closest shipwrecks. This is easy with MongoDB! It supports GeoJSON like postgres.

Even though I set the center to Haiti, there are no shipwrecks on your screen. We need to call our Realm instance! This couldn't be easier, as we did most of our configuration in `main.js`. We need to add two things to our import statement:

```js
import gql from "graphql-tag";
import { useQuery, useResult } from "@vue/apollo-composable";
```

Looking back at the Vue3 Apollo Docs, we can learn all about [queries](https://v4.apollo.vuejs.org/guide-composable/query.html). Hopefully, right now something is clicking, Realm has already automated a gnarly part of GraphQL, creating a query. For our example, we can leave out almost all of the fields, but just copy it from the GraphQL tab!

```js
const { result} = useQuery(gql`
  query AllShips {
    shipwrecks {
      _id
      chart
      coordinates
   
    }
  }
`);
```
We could get everything out of that result object, but I [recommend using](https://v4.apollo.vuejs.org/api/use-result.html) `const points = useResult(result)`. Instead of dealing with iteration, all the info you need is right there. Also, `useResult` can help with returning errors. Now we need to use a [computed property](https://v3.vuejs.org/guide/computed.html). Computed properties watch for value changes and react to it, furthermore, it caches the result so it can help with performance. In the computed property we are going to map out the coordinates. If you think about it, we are creating an array of arrays. 

```js
const messages = computed(() => points.value ? points.value.map(x => x.coordinates) : [[]])
```

The last thing we need to do is set coordinates to equal our value messages. 

```js
          <ol-geom-multi-point :coordinates="messages"></ol-geom-multi-point>
```

If we click save and navigate back to our browser, we should see the shipwrecks! It is not pretty but we are calling out database and getting back info, mapping it, and our web app is reacting to it. 

In the past 4 tutorials we have gone from nothing, to having a website that is visualizing shipwreck data from a database we control. We are using the latest in greatest in Vue; utilizing `<script setup>`, the composition API, and GraphQL. We also set up authorization, cleaned our data, and validated schemas. In part 5 I can either show deploying this app using Realm, or using Mongo to query data based on Longitude and Latitude. Let me know!

Just for posterity sake, here is my `<script>` section. 
```js
<script setup>
import { useQuery, useResult } from "@vue/apollo-composable";
import { computed } from 'vue'
import gql from "graphql-tag";
const { result} = useQuery(gql`
  query AllShips {
    shipwrecks {
      _id
      chart
      coordinates
   
    }
  }
`);
const points = useResult(result)
const messages = computed(() => points.value ? points.value.map(x => x.coordinates) : [[]])
const center = [-72.333336, 18.533333];
const projection = "EPSG:4326";
const zoom = 9;
const rotation = 0;
const radius = 10;
const strokeWidth = 4;
const strokeColor = "red";
const fillColor = "white";
</script>
```

What we also can do is increase the "height" value in 
```js
<ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="height: 1000px"
  >
```

To clean up, we can go back to App.vue and comment out the `<img>` element and check out the state of our site. Looking much better. I hope this maybe sets up a love for Vue for one person - that would be sick!

Nicholas Oxford