How to create a chat app in twenty minutes

image



My dad likes to remind me that, as a computer engineer in the 1970s, " he was a programmer before programming was fashionable ." He even showed old Fortran and COBOL scripts a couple of times. After reading this code, I can confidently say that programming is definitely cooler today .



A hallmark of modern programming languages ​​and development environments is how much less code a developer has to write. By using high-level languages ​​along with the many available APIs, open source packages, and paid services, applications - even those with complex requirements - can be built fairly quickly.



A comparison to demonstrate the evolution of software development is construction. Once upon a time, the construction of any house began with cutting down trees on your site. However, materials, tools and methods quickly appeared so that construction was completed faster, objects became stronger, and workers were freed from some elementary tasks.



How many skyscrapers would be built if the builders mined their own steel?



Software developers, who continue to work to this day, at the dawn of their careers "cut their own trees". At the same time, unprecedented innovations of the last decade have led to the fact that the software industry began to develop in much the same way as construction.



Simply put, today's developers now have the tools, techniques, and best practices to complete projects faster, get stable applications, and save developers from low-level tasks.



How to make a chat app



Let's quickly create something that used to take days or weeks. We will make a Public Chat Room application that uses WebSockets for real-time messaging.



WebSockets are natively supported by all modern browsers. However, our goal is to find out what tools we can use at work, not reinvent them . With this in mind, we will use the following technologies:





The starter project and complete README file can be found in this GitHub repository . If you only want to see the finished application, take a look at the public-chat-room branch.



In addition, the video below (in English) explains each step in more detail.



Let's start.



Seven steps to create a chat application:



1. Project setup



Clone the starter project and go to the group chat directory. You can decide for yourself whether to use yarn or npm to install project dependencies. In any case, we need all the NPM packages specified in the package.json file.



#  
git clone https://github.com/8base/Chat-application-using-GraphQL-Subscriptions-and-Vue.git group-chat
#   
cd group-chat
#  
yarn


To interact with the GraphQL API, we need to set up three environment variables. Create a .env.local file in the root directory with the following command, and the Vue application, after initialization, will automatically set the environment variables we added to this file. Both values and should not be changed. You only need to set the value . If you have an 8base workspace that you want to use to create a chat application using our tutorial, update the .env.local file with your workspace ID. If not, get the workspace ID by following steps 1 and 2 of 8base Quickstart .



echo 'VUE_APP_8BASE_WORKSPACE_ID=<YOUR_8BASE_WORKSPACE_ID>

VUE_APP_8BASE_API_ENDPOINT=https://api.8base.com

VUE_APP_8BASE_WS_ENDPOINT=wss://ws.8base.com' \

> .env.local




VUE_APP_8BASE_API_ENDPOINTVUE_APP_8BASE_WS_ENDPOINTVUE_APP_8BASE_WORKSPACE_ID







2. Import schema



Now we need to prepare the server side. At the root of this repository, you should find the file chat-schema.json. To import it into the workspace, you just need to install the 8base command line and login, and then import the schema file.



#  8base CLI
yarn global add 8base-cli
#  CLI
8base login
#      
8base import -f chat-schema.json -w <YOUR_8BASE_WORKSPACE_ID>


3. API access



The final task for the backend is to allow public access to the GraphQL API.



In 8base console go to App Services > Roles > Guest. Update the permissions set for both posts and users to be either checked or set as All Records (as shown in the screenshot below).



The Guest role determines what the user who makes an unauthenticated API request is allowed to do.



image

Role editor in 8base console.



4. Writing GraphQL queries



In this step, we are going to define and write out all the GraphQL queries that we will need for our chat component. This will help us understand what data we will read, create and listen to (via WebSockets) using the API.



The following code should be placed in a file src / utils / graphql.js. Read the comments above each exported constant to understand what each query does.




/* gql      graphQL */
import gql from "graphql-tag";
‍
/* 1.    -   10  */
export const InitialChatData = gql`
{
  usersList {
    items {
      id
      email
    }
  }
  messagesList(last: 10) {
    items {
      content
      createdAt
      author {
        id
        email
      }
    }
  }
}
`;
‍
/* 2.          */
export const CreateUser = gql`
mutation($email: String!) {
  userCreate(data: { email: $email, roles: { connect: { name: "Guest" } } }) {
    id
  }
}
`;
‍
/* 3.   */
export const DeleteUser = gql`
mutation($id: ID!) {
  userDelete(data: { id: $id, force: true }) {
    success
  }
}
`;
‍
/* 4.        */
export const UsersSubscription = gql`
subscription {
  Users(filter: { mutation_in: [create, delete] }) {
    mutation
    node {
      id
      email
    }
  }
}
`;
‍
/* 5.          */
export const CreateMessage = gql`
mutation($id: ID!, $content: String!) {
  messageCreate(
    data: { content: $content, author: { connect: { id: $id } } }
  ) {
    id
  }
}
`;
‍
/* 6.     . */
export const MessagesSubscription = gql`
subscription {
  Messages(filter: { mutation_in: create }) {
    node {
      content
      createdAt
      author {
        id
        email
      }
    }
  }
}
`;




5. Configuring Apollo client for subscriptions



With our GraphQL queries written, it's time to set up our API modules.



First, let's tackle the API client ApolloClientwith its required defaults. For createHttpLinkus we provide our fully formed workspace endpoint. This code is in src/utils/api.js.



import { ApolloClient } from "apollo-boost";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
‍
const { VUE_APP_8BASE_API_ENDPOINT, VUE_APP_8BASE_WORKSPACE_ID } = process.env;

export default new ApolloClient({
link: createHttpLink({
  uri: `${VUE_APP_8BASE_API_ENDPOINT}/${VUE_APP_8BASE_WORKSPACE_ID}`,
}),
cache: new InMemoryCache(),
});

// Note:     ,    // ApolloClient,    .


Then we will deal with the subscription client using subscriptions-transport-wsand isomorphic-ws. This code is a little longer than the previous one, so it's worth taking the time to read the comments in the code.



We initialize SubscriptionClientusing our WebSockets endpoint and workspaceIdin parameters connectionParams. We then use this one subscriptionClientin two methods defined in the default export: subscribe()and close().



subscribeallows us to create new subscriptions with data and error callbacks. The close method is what we can use to close the connection when we leave the chat.



import WebSocket from "isomorphic-ws";
import { SubscriptionClient } from "subscriptions-transport-ws";
‍
const { VUE_APP_8BASE_WS_ENDPOINT, VUE_APP_8BASE_WORKSPACE_ID } = process.env;

/**
*   ,  
*     .
*/

const subscriptionClient = new SubscriptionClient(
VUE_APP_8BASE_WS_ENDPOINT,
{
  reconnect: true,
  connectionParams: {
    /**
      * Workspace ID    ,  
*  Websocket  
*    
      */
    workspaceId: VUE_APP_8BASE_WORKSPACE_ID,
  },
},
/**
  *    WebSocket,   W3C. * ,        *WebSocket (,   NodeJS)
  */
WebSocket
);
‍
export default {
/**
  *   ,      *'data’  'error’
  */
subscribe: (query, options) => {
  const { variables, data, error } = options;
‍
  /**
    *     .
    */
  const result = subscriptionClient.request({
    query,
    variables,
  });
‍
  /**
    *       * ,     , 
* subscriptionClient
    */
  const { unsubscribe } = result.subscribe({
    /**
      *       
* ,  .
      */
    next(result) {
      if (typeof data === "function") {
        data(result);
      }
    },
    /**
      *          ,  .
      */
    error(e) {
      if (typeof error === "function") {
        error(e);
      }
    },
  });
‍
  return unsubscribe;
},
‍
/**
  *  subscriptionClient .
  */
close: () => {
  subscriptionClient.close();
},
};
// .     SubscriptionClient   , 
// ,    .


6. Writing a Vue Component



We now have everything we need to create a public chat. There is only one component left to write GroupChat.vue.



Load the component using yarn serve and let's continue.



Important note: everyone has their own idea of ​​beauty, so I only made the minimal styles necessary for the component to be functional.



Component script



First, we need to import our modules, simple styles, and GraphQL queries. All this is in ours src / utils.

Declare the following imports in GroupChat.vue.



/* API  */
import Api from "./utils/api";
import Wss from "./utils/wss";

/* graphQL  */
import {
InitialChatData,
CreateUser,
DeleteUser,
UsersSubscription,
CreateMessage,
MessagesSubscription,
} from "./utils/graphql";
‍
/*  */
import "../assets/styles.css";


Component data



We can define which data properties we want to use in the data function of our component. All we need is a way to store chat users, messages, the name of the "current" user, and any message that hasn't been sent yet. These properties can be added as follows:



/* imports ... */

export default {
name: "GroupChat",
‍
data: () => ({
  messages: [],
  newMessage: "",
  me: { email: "" },
  users: [],
}),
};


Lifecycle hooks



Our lifecycle hooks run at different points in the life of a Vue component. For example when it is mounted or updated. In this case, we are only interested in the creation and beforeDestroycomponent. In such cases, we want to either open the chat subscriptions or close them.



/* ... */

export default {
/*   ... */

/**
  *   ,    .
  */
created() {
  /**
    *   ,       
    */
  Wss.subscribe(UsersSubscription, {
    data: this.handleUser,
  });
  /**
    *   ,     
    */
  Wss.subscribe(MessagesSubscription, {
    data: this.addMessage,
  });
  /**
    *     (   10 )
    */
  Api.query({
    query: InitialChatData,
  }).then(({ data }) => {
    this.users = data.usersList.items;
    this.messages = data.messagesList.items;
  });
  /**
    *     ,   
    */
  window.onbeforeunload = this.closeChat;
},
‍
/**
  *   ,    .
  */
beforeDestroy() {
  this.closeChat();
},
};


Component methods



We need to add some methods to process each call / API response ( createMessage, addMessage, closeChat, etc.). All of them will be stored in the method object of our component.

It is necessary to

note one thing: most mutations do not wait and do not handle the answers. This is because we have subscriptions that track these mutations. After a successful launch, event data is processed by the subscription.

Most

of these methods speak for themselves. Anyway, read the comments in the following code.



/*  ... */

export default {
/*   ... */
‍
methods: {
  /**
    *   ,     .
    */
  createUser() {
    Api.mutate({
      mutation: CreateUser,
      variables: {
        email: this.me.email,
      },
    });
  },
  /**
    *     ID.
    */
  deleteUser() {
    Api.mutate({
      mutation: DeleteUser,
      variables: { id: this.me.id },
    });
  },
  /**
    *        ,   
*           
* .
*
*    ,      ,  
*   ,   .
    */
  handleUser({
    data: {
      Users: { mutation, node },
    },
  }) {
    ({
      create: this.addUser,
      delete: this.removeUser,
    }[mutation](node));
  },
  /**
    *      users,  , *     .
    */
  addUser(user) {
    if (this.me.email === user.email) {
      this.me = user;
    }
    this.users.push(user);
  },
  /**
    *     users  ID.
    */
  removeUser(user) {
    this.users = this.users.filter(
      (p) => p.id != user.id
    );
  },
  /*    */
  createMessage() {
    Api.mutate({
      mutation: CreateMessage,
      variables: {
        id: this.me.id,
        content: this.newMessage,
      },
    }).then(() => (this.newMessage = ""));
  },
  /**
    *        .  * ,    ,       *.
    */
  addMessage({
    data: {
      Messages: { node },
    },
  }) {
    this.messages.push(node);
  },
  /**
    *        .          beforeDestroy     .
    */
  closeChat () {
    /*     */
    Wss.close()
    /*   */
    this.deleteUser();
    /*     */
    this.me = { me: { email: '' } }
  }
},
‍
/*  ... */
}


Component template



Last but not least, we have a component GroupChat.vue.

There are

thousands of great tutorials on how to create beautiful user interfaces. This is not one of them.

The following

pattern matches the minimum requirements for the chat application. To make it beautiful or not is up to you. That said, let's quickly go over the key markup we've implemented here.

As

always, read the inline comments to the code.



<template>
<div id="app">
  <!--
           ,     .      ..
    -->
  <div v-if="me.id" class="chat">
    <div class="header">
      <!--
           ,      ,  ,     ,   .
        -->
      {{ users.length }} Online Users
      <!--
           ,   closeChat..
        -->
      <button @click="closeChat">Leave Chat</button>
    </div>
    <!--
     ,      ,      div.  ,         ,     me.
      -->
    <div
      :key="index"
      v-for="(msg, index) in messages"
      :class="['msg', { me: msg.participant.id === me.id }]"
    >
      <p>{{ msg.content }}</p>
      <small
        ><strong>{{ msg.participant.email }}</strong> {{ msg.createdAt
        }}</small
      >
    </div>
    <!--
      newMessage.
      -->
    <div class="input">
      <input
        type="text"
        placeholder="Say something..."
        v-model="newMessage"
      />
      <!--
           ,    createMessage.
        -->
      <button @click="createMessage">Send</button>
    </div>
  </div>
  <!--
          .     ,   createUser.
    -->
  <div v-else class="signup">
    <label for="email">Sign up to chat!</label>
    <br />
    <input
      type="text"
      v-model="me.email"
      placeholder="What's your email?"
      @blur="createUser"
      required
    />
  </div>
</div>
</template>


And now the public chat is built. If you open it on your local network, you can start sending and receiving messages. However, to prove that this is a real group chat, open multiple windows and watch the conversation progress.



7. Conclusion and testing



In this tutorial, we've explored how using modern development tools allows us to create real-world applications in minutes.



I hope you also learned how to initialize ApolloClientand SubscriptionClientexecute GraphQL queries, mutations and subscriptions efficiently in an 8base workspace, as well as a little about VueJS.



Whether you're working on a mobile game, messengers, notification apps, or other projects that require real-time data, subscriptions are a great tool. And now we have just begun to consider them.



Build a chat app with 8base



8base is a turnkey serverless backend as a service built by developers for developers. The 8base platform enables developers to build stunning cloud applications using JavaScript and GraphQL. Learn more about the 8base platform here .



All Articles