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_ENDPOINT
VUE_APP_8BASE_WS_ENDPOINT
VUE_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.
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
ApolloClient
with its required defaults. For createHttpLink
us 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-ws
and 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
SubscriptionClient
using our WebSockets endpoint and workspaceId
in parameters connectionParams
. We then use this one subscriptionClient
in two methods defined in the default export: subscribe()
and close()
.
subscribe
allows 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
beforeDestroy
component. 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
ApolloClient
and SubscriptionClient
execute 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 .