AWS Amplify Subscriptions Usage / 2. Multi-Room Chat
Tags: aws, amplify, vue, graphql
Published: 2021-02-11 04:40:53

We can use the Subscriptions feature with AWS Amplify.
It's easy for us to get started, but it can be complicated.

[Previous article](https://dev.to/tacck/aws-amplify-subscriptions-usage-1-open-chat-aa6), I show `Open Chat`.

In this article, I'll show you `Multi-Room Chat` sample.
And, there are two way implementations, one is "Easy" way, and another is "Efficient" way.

My repository:
{% github tacck/sample-amplify-subscriptions no-readme %}

**TOC**

| Section | Title |
|:---:| --- |
| 2.1 | [Implementations for "Easy" way](#section-2-1) |
| 2.2 | [Implementations for "Efficient" way](#section-2-2) |

---

## 2.1 Implementations for "Easy" way <a name="section-2-1"></a>

### Edit GraphQL scheme file

Add a type for Multi-Room Chat.

[amplify/backend/api/sampleamplifysubscri/schema.graphql](https://github.com/tacck/sample-amplify-subscriptions/blob/2-1-multiroom-chat-filter/amplify/backend/api/sampleamplifysubscri/schema.graphql#L6-L10)

``` graphql
type RoomChat @model {
  id: ID!
  roomName: String!
  message: String!
}
```

And, push project.

``` bash
$ amplify push
Initializing new Amplify CLI version...
Done initializing new version.
Scanning for plugins...
Plugin scan successful
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name        | Operation | Provider plugin   |
| -------- | -------------------- | --------- | ----------------- |
| Api      | sampleamplifysubscri | Update    | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your s
chema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes
⠼ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud

GraphQL endpoint: https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.ap-northeast-1.amazonaws.com/graphql
GraphQL API KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

$ 
```

### Write Multi-room Open Chat for "Easy" way

[src/views/OpenChat.vue](https://github.com/tacck/sample-amplify-subscriptions/blob/2-1-multiroom-chat-filter/src/views/OpenChat.vue)

``` vue
<template>
  <v-container>
    <v-row>
      <v-col cols="12">
        <v-card>
          <v-card-title>Multi-room Open Chat</v-card-title>
          <v-card-text
            >Anyone can use this chat. All subscriptions are receved, and
            received messages are set in the message list for each
            room.</v-card-text
          >
        </v-card>
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="12">
        <v-text-field
          v-model="inputMessage"
          label="New Message"
          outlined
          clearable
          append-outer-icon="mdi-send"
          @click:append-outer="sendMessage"
        ></v-text-field>
      </v-col>
    </v-row>
    <v-tabs
      v-model="roomName"
      background-color="primary"
      center-active
      centered
      dark
    >
      <v-tab href="#room1">room1</v-tab>
      <v-tab href="#room2">room2</v-tab>
    </v-tabs>
    <v-card flat>
      <v-tabs-items v-model="roomName">
        <v-tab-item value="room1">
          <v-row class="pa-2">
            <v-col cols="6">
              <ChatList title="Input" :list="messages.room1"></ChatList>
            </v-col>
            <v-col cols="6">
              <ChatList
                title="Subscriptions"
                :list="subscriptionMessages.room1"
              ></ChatList>
            </v-col>
          </v-row>
        </v-tab-item>
        <v-tab-item value="room2">
          <v-row class="pa-2">
            <v-col cols="6">
              <ChatList title="Input" :list="messages.room2"></ChatList>
            </v-col>
            <v-col cols="6">
              <ChatList
                title="Subscriptions"
                :list="subscriptionMessages.room2"
              ></ChatList>
            </v-col>
          </v-row>
        </v-tab-item>
      </v-tabs-items>
    </v-card>
  </v-container>
</template>

<script>
import { API, graphqlOperation } from 'aws-amplify'
import { createRoomChat } from '@/graphql/mutations'
import { onCreateRoomChat } from '@/graphql/subscriptions'

import ChatList from '@/components/ChatList'

export default {
  components: { ChatList },
  data: function() {
    return {
      roomName: null,
      inputMessage: '',
      messages: {
        room1: [],
        room2: [],
      },
      subscriptionMessages: {
        room1: [],
        room2: [],
      },
      onCreateOpenChatSubscription: null,
    }
  },
  created: function() {
    this.onCreateOpenChatSubscription = API.graphql(
      graphqlOperation(onCreateRoomChat),
    ).subscribe({
      next: ({ provider, value }) => {
        console.log({ provider, value })
        this.subscriptionMessages[value.data.onCreateRoomChat.roomName].push(
          value.data.onCreateRoomChat,
        )
      },
    })
  },
  beforeDestroy: function() {
    if (this.onCreateOpenChatSubscription) {
      this.onCreateOpenChatSubscription.unsubscribe()
      this.onCreateOpenChatSubscription = null
    }
  },
  methods: {
    sendMessage: async function() {
      const message = await API.graphql(
        graphqlOperation(createRoomChat, {
          input: { message: this.inputMessage, roomName: this.roomName },
        }),
      )
      console.log(message)

      this.messages[this.roomName].push(message.data.createRoomChat)
      this.inputMessage = ''
    },
  },
}
</script>

<style></style>
```

> See other files:
> https://github.com/tacck/sample-amplify-subscriptions/tree/2-1-multiroom-chat-filter

The important point is here.

``` vue
(snip)

  created: function() {
    this.onCreateOpenChatSubscription = API.graphql(
      graphqlOperation(onCreateRoomChat),
    ).subscribe({
      next: ({ provider, value }) => {
        console.log({ provider, value })
        this.subscriptionMessages[value.data.onCreateRoomChat.roomName].push(
          value.data.onCreateRoomChat,
        )
      },
    })
  },

(snip)

  methods: {
    sendMessage: async function() {
      const message = await API.graphql(
        graphqlOperation(createRoomChat, {
          input: { message: this.inputMessage, roomName: this.roomName },
        }),
      )
      console.log(message)

      this.messages[this.roomName].push(message.data.createRoomChat)
      this.inputMessage = ''
    },
  },

(snip)
```

In `sendMessage` function, send message with `roomName` and set message in `this.messages[this.roomName]` list.

In `created` function, received message by subscription and set message in `this.subscriptionMessages[value.data.onCreateRoomChat.roomName]` list.

It's just filtering by `roomName`.
Very Easy way 😃.

However, there is a problem with this way.

We receive messages for "ALL" rooms.
When you see "Room2", you will not want to receive "Room1"'s messages.

Then, We implement for "Efficient" way.

---

## 2.2 Implementations for "Efficient" way <a name="section-2-2"></a>

### Edit GraphQL scheme file

Add a type Subscription.

[amplify/backend/api/sampleamplifysubscri/schema.graphql](https://github.com/tacck/sample-amplify-subscriptions/blob/2-2-multiroom-chat-subscription/amplify/backend/api/sampleamplifysubscri/schema.graphql#L12-L15)

``` graphql
type Subscription {
  onCreateRoomChatByRoomName(roomName: String!): RoomChat
    @aws_subscribe(mutations: ["createRoomChat"])
}
```

This mean: When you use "createRoomChat", the server return data `RoomChat` that you created.

You can use `onCreateRoomChatByRoomName` instead of `onCreateOpenChat`.

And, push project.

``` bash
$ amplify push
Scanning for plugins...
Plugin scan successful
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name        | Operation | Provider plugin   |
| -------- | -------------------- | --------- | ----------------- |
| Api      | sampleamplifysubscri | Update    | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes
⠸ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud

GraphQL endpoint: https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.ap-northeast-1.amazonaws.com/graphql
GraphQL API KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


$
```

### Write Multi-room Open Chat for "Efficient" way

[src/views/OpenChat.vue](https://github.com/tacck/sample-amplify-subscriptions/blob/2-2-multiroom-chat-subscription/src/views/OpenChat.vue)

``` vue
<template>
  <v-container>
    <v-row>
      <v-col cols="12">
        <v-card>
          <v-card-title>Multi-room Open Chat</v-card-title>
          <v-card-text
            >Anyone can use this chat. Now, we receive messages only selected
            room.
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="12">
        <v-text-field
          v-model="inputMessage"
          label="New Message"
          outlined
          clearable
          append-outer-icon="mdi-send"
          @click:append-outer="sendMessage"
        ></v-text-field>
      </v-col>
    </v-row>
    <v-tabs
      v-model="roomName"
      background-color="primary"
      center-active
      centered
      dark
    >
      <v-tab
        v-for="(room, index) in rooms"
        :key="index"
        :href="'#' + room"
        @click="setSubscribeByRoomName(room)"
        >{{ room }}</v-tab
      >
    </v-tabs>
    <v-card flat>
      <v-tabs-items v-model="roomName">
        <v-tab-item v-for="(room, index) in rooms" :key="index" :value="room">
          <v-row class="pa-2">
            <v-col cols="6">
              <ChatList title="Input" :list="messages[room]"></ChatList>
            </v-col>
            <v-col cols="6">
              <ChatList
                title="Subscriptions"
                :list="subscriptionMessages[room]"
              ></ChatList>
            </v-col>
          </v-row>
        </v-tab-item>
      </v-tabs-items>
    </v-card>
  </v-container>
</template>

<script>
import { API, graphqlOperation } from 'aws-amplify'
import { createRoomChat } from '@/graphql/mutations'
import { onCreateRoomChatByRoomName } from '@/graphql/subscriptions'

import ChatList from '@/components/ChatList'

export default {
  components: { ChatList },
  data: function() {
    return {
      roomName: null,
      inputMessage: '',
      rooms: ['room1', 'room2'],
      messages: {
        room1: [],
        room2: [],
      },
      subscriptionMessages: {
        room1: [],
        room2: [],
      },
      onCreateMultiRoomChatSubscriptions: {
        room1: null,
        room2: null,
      },
    }
  },
  created: function() {
    this.setSubscribeByRoomName('room1')
  },
  beforeDestroy: function() {
    this.clearSubscriptions()
  },
  methods: {
    sendMessage: async function() {
      const message = await API.graphql(
        graphqlOperation(createRoomChat, {
          input: { message: this.inputMessage, roomName: this.roomName },
        }),
      )
      console.log(message)

      this.messages[this.roomName].push(message.data.createRoomChat)
      this.inputMessage = ''
    },
    setSubscribeByRoomName(roomName) {
      this.clearSubscriptions()

      this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql(
        graphqlOperation(onCreateRoomChatByRoomName, { roomName: roomName }),
      ).subscribe({
        next: ({ provider, value }) => {
          console.log({ provider, value })
          this.subscriptionMessages[
            value.data.onCreateRoomChatByRoomName.roomName
          ].push(value.data.onCreateRoomChatByRoomName)
        },
      })
    },
    clearSubscriptions() {
      this.rooms.forEach(room => {
        if (this.onCreateMultiRoomChatSubscriptions[room]) {
          this.onCreateMultiRoomChatSubscriptions[room].unsubscribe()
        }
        this.onCreateMultiRoomChatSubscriptions[room] = null
      })
    },
  },
}
</script>

<style></style>
```

> See other files:
> https://github.com/tacck/sample-amplify-subscriptions/tree/2-2-multiroom-chat-subscription

The important point is here.

``` vue
(snip)

      <v-tab
        v-for="(room, index) in rooms"
        :key="index"
        :href="'#' + room"
        @click="setSubscribeByRoomName(room)"
        >{{ room }}</v-tab
      >

(snip)

    setSubscribeByRoomName(roomName) {
      this.clearSubscriptions()

      this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql(
        graphqlOperation(onCreateRoomChatByRoomName, { roomName: roomName }),
      ).subscribe({
        next: ({ provider, value }) => {
          console.log({ provider, value })
          this.subscriptionMessages[
            value.data.onCreateRoomChatByRoomName.roomName
          ].push(value.data.onCreateRoomChatByRoomName)
        },
      })
    },

(snip)
```

In `setSubscribeByRoomName` function, we use `onCreateMultiRoomChatSubscriptions` with `roomName`.

Only this change, we receive selected room messages.

Very simple but efficient way.

Next, we see subscriptions with `@auth` directive.