DEV Community

Kihara, Takuya
Kihara, Takuya

Posted on

AWS Amplify Subscriptions Usage / 2. Multi-Room Chat

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

Previous article, 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 logo tacck / sample-amplify-subscriptions

Sample code for this article. https://dev.to/tacck/series/11192

TOC


2.1 Implementations for "Easy" way

Edit GraphQL scheme file

Add a type for Multi-Room Chat.

amplify/backend/api/sampleamplifysubscri/schema.graphql

type RoomChat @model {
  id: ID!
  roomName: String!
  message: String!
}
Enter fullscreen mode Exit fullscreen mode

And, push project.

$ 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

$ 
Enter fullscreen mode Exit fullscreen mode

Write Multi-room Open Chat for "Easy" way

src/views/OpenChat.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>
Enter fullscreen mode Exit fullscreen mode

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

The important point is here.

(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)
Enter fullscreen mode Exit fullscreen mode

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

Edit GraphQL scheme file

Add a type Subscription.

amplify/backend/api/sampleamplifysubscri/schema.graphql

type Subscription {
  onCreateRoomChatByRoomName(roomName: String!): RoomChat
    @aws_subscribe(mutations: ["createRoomChat"])
}
Enter fullscreen mode Exit fullscreen mode

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

You can use onCreateRoomChatByRoomName instead of onCreateOpenChat.

And, push project.

$ 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


$
Enter fullscreen mode Exit fullscreen mode

Write Multi-room Open Chat for "Efficient" way

src/views/OpenChat.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>
Enter fullscreen mode Exit fullscreen mode

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

The important point is here.

(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)
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)