Создайте приложение для чата, используя GraphQL, VueJS и мощь современных инструментов разработки программного обеспечения.
Мой отец любит напоминать мне, что, будучи компьютерным инженером в 1970-х, «он был кодером до того, как программирование стало крутым». Один или два раза он даже вытащил старые скрипты Fortran и COBOL. Прочитав этот код, я с уверенностью могу сказать, что кодирование сегодня определенно круче!
Замечательная черта современных компьютерных языков и сред разработки - это то, насколько меньше кода они позволяют писать разработчику. Используя языки высокого уровня вместе с множеством доступных API, пакетов с открытым исходным кодом и платных сервисов, приложения - даже со сложными требованиями - можно создавать невероятно быстро.
Полезным сравнением для осмысления этого момента в эволюции разработки программного обеспечения является рассмотрение конструкции. Когда-то строительство любого дома начиналось с вырубки собственных деревьев. Тем не менее, стандартные материалы, инструменты и передовые методы, разработанные для ускорения выполнения проектов, структур представляют собой более сложную задачу и освобождают рабочих от задач более низкого уровня.
Сколько было бы небоскребов, если бы для строительства одного из них пришлось добывать собственную сталь?
Инженеры, которые еще молоды и работают, начинали свою карьеру с рубки деревьев. Тем не менее, беспрецедентные инновации последнего десятилетия привели к тому, что индустрия программного обеспечения стала созревать так же, как строительство.
Проще говоря, у современных разработчиков теперь есть стандартные инструменты, материалы и передовые методы, которые позволяют быстрее завершать проекты, стабильно работать с приложениями и облегчают разработчикам выполнение задач более низкого уровня.
Обзор учебного пособия по чат-комнате
Давайте построим что-нибудь за считанные минуты, что традиционно занимало бы дни или недели; хорошие новости, это не приложение To-Do! Мы собираемся создать приложение Public Chat Room, которое использует WebSockets для обмена сообщениями в реальном времени.
WebSockets изначально поддерживаются всеми современными браузерами. Однако наша цель - показать, какие инструменты мы можем использовать в работе, а не развивать ее. В этом духе мы будем использовать следующие технологии:
Стартовый проект и полный файл README можно найти в этом репозитории GitHub. Если вы хотите просмотреть только готовое приложение, загляните в ветку public-chat-room
.
Кроме того, в приведенном ниже видео рассматривается руководство и объясняется каждый шаг в более подробном контексте.
Тем не менее, поехали!
1. Настройка проекта
Клонируйте стартовый проект и перейдите в каталог группового чата. Независимо от того, используете ли вы yarn
или npm
для установки зависимостей проекта, решать вам. В любом случае нам нужно установить все пакеты NPM, объявленные в файле package.json
.
# Clone project git clone https://github.com/8base/Chat-application-using-GraphQL-Subscriptions-and-Vue.git group-chat # Move into directory cd group-chat # Install dependencies yarn
Чтобы взаимодействовать с GraphQL API, мы должны установить 3 переменные среды. Создайте файл .env.local
в корневом каталоге проекта с помощью следующей команды. Приложение Vue автоматически установит переменные среды, которые мы добавляем в этот файл после инициализации.
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
.
Если у вас есть рабочее пространство 8base, которое вы хотите использовать для этого учебника, продолжайте и обновите свой .env.local
файл с идентификатором рабочего пространства. В противном случае получите идентификатор рабочего пространства, выполнив шаги 1 и 2 8base Quickstart.
2. Импорт схемы базы данных
Теперь нам нужно подготовить серверную часть. В корне этого репо вы должны найти файл chat-schema.json
. Чтобы импортировать его в рабочую область, нам просто нужно установить и аутентифицировать командную строку 8base, а затем импортировать файл схемы.
# Install 8base CLI yarn global add 8base-cli # Authenticate CLI 8base login # Configure project and select workspace 8base configure # Import the schema to our workspace 8base import -f chat-schema.json -w <YOUR_8BASE_WORKSPACE_ID>
3. Настройка разрешений API
Последняя задача бэкенда - разрешить публичный доступ к GraphQL API.
В консоли 8base перейдите к App Services > Roles > Guest
. Обновите разрешения, установленные для Сообщения и Пользователи, чтобы они были отмечены или Все записи (как показано на снимке экрана). ниже).
Роль гостя определяет, что разрешено делать пользователю, отправившему неаутентифицированный запрос к API.
4. Написание запросов GraphQL
На этом этапе мы собираемся определить и выписать все запросы GraphQL, которые нам понадобятся для нашего компонента чата. Это поможет нам понять, какие данные мы будем читать, создавать и слушать (через WebSockets) через API.
В файл src/utils/graphql.js
следует поместить следующий код. Прочтите комментарии над каждой экспортированной константой, чтобы понять, что выполняет каждый запрос.
/* gql converts the query strings into graphQL documents */ import gql from "graphql-tag"; /* 1. Fetch all online chat Users and last 10 messages */ export const InitialChatData = gql` { usersList { items { id email } } messagesList(last: 10) { items { content createdAt author { id email } } } } `; /* 2. Create new chat users and assign them the Guest role */ export const CreateUser = gql` mutation($email: String!) { userCreate(data: { email: $email, roles: { connect: { name: "Guest" } } }) { id } } `; /* 3. Delete a chat user */ export const DeleteUser = gql` mutation($id: ID!) { userDelete(data: { id: $id, force: true }) { success } } `; /* 4. Listen for when chat users are created or deleted */ export const UsersSubscription = gql` subscription { Users(filter: { mutation_in: [create, delete] }) { mutation node { id email } } } `; /* 5. Create new chat messages and connect it to it's author */ export const CreateMessage = gql` mutation($id: ID!, $content: String!) { messageCreate( data: { content: $content, author: { connect: { id: $id } } } ) { id } } `; /* 6. Listen for when chat messages are created. */ export const MessagesSubscription = gql` subscription { Messages(filter: { mutation_in: create }) { node { content createdAt author { id email } } } } `;
5. Создание Apollo и SubscriptionClient
Когда наши запросы GraphQL написаны, пришло время настроить наши модули API.
Во-первых, давайте разберемся с клиентом API, использующим ApolloClient
с его обязательными настройками по умолчанию. Для createHttpLink
мы предоставляем нашу полностью сформированную конечную точку рабочего пространства. Этот код принадлежит 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: To learn more about the options available when configuring * ApolloClient, please reference their documentation. */
Затем давайте займемся клиентом подписки с помощью subscriptions-transport-ws
и isomorphic-ws
. Сценарий немного длиннее, чем наш предыдущий, поэтому, пожалуйста, найдите время, чтобы прочитать комментарии в коде!
Мы инициализируем SubscriptionClient
с помощью нашей конечной точки WebSockets и workspaceId
в connectionParams
. Затем мы используем этот subscriptionClient
в двух методах, определенных для экспорта по умолчанию; subscribe()
и close()
.
subscribe
позволяет нам создавать новые подписки с обратными вызовами данных и ошибок. Метод закрытия - это то, что мы можем использовать, чтобы закрыть соединение при выходе из комнаты чата.
import WebSocket from "isomorphic-ws"; import { SubscriptionClient } from "subscriptions-transport-ws"; const { VUE_APP_8BASE_WS_ENDPOINT, VUE_APP_8BASE_WORKSPACE_ID } = process.env; /** * Create the subscription client using the relevant environment * variables and default options */ const subscriptionClient = new SubscriptionClient( VUE_APP_8BASE_WS_ENDPOINT, { reconnect: true, connectionParams: { /** * WorkspaceID MUST be set or the Websocket Endpoint * wont be able to map the request to the appropriate * workspace */ workspaceId: VUE_APP_8BASE_WORKSPACE_ID } }, /** * Constructor for W3C compliant WebSocket implementation. * Use this when your environment does not have a built-in * native WebSocket (for example, with NodeJS client) */ WebSocket ); export default { /** * Accepts the subscription query, any variables and the * callback handlers 'data' and 'error' */ subscribe: (query, options) => { const { variables, data, error } = options; /** * Runs the new subscription request. */ const result = subscriptionClient.request({ query, variables }); /** * The unsubscribe function can be used to close a specific * subscription as opposed to ALL subscriptions be maintained * by the subscriptionClient */ const { unsubscribe } = result.subscribe({ /** * Whenever an event is received, the result is passed * to the developer specified data callback. */ next(result) { if (typeof data === "function") { data(result); } }, /** * Whenever an error is received, the error is passed * to the developer specified error callback. */ error(e) { if (typeof error === "function") { error(e); } } }); return unsubscribe; }, /** * Closes subscriptionClient's connection. */ close: () => { subscriptionClient.close(); } }; /** * Note: To learn more about the SubscriptionClient and * its options, please reference their documentation */
6. Написание компонента Vue
На данный момент у нас есть все необходимое для создания нашего публичного чата! Осталось только написать один GroupChat.vue
компонент.
Загрузите компонент с yarn serve
и продолжим.
ВНИМАНИЕ: красота в глазах смотрящего… и поэтому добавлен только минимальный стиль, необходимый для того, чтобы компонент работал.
Скрипт компонента Vue
Сначала нам нужно импортировать наши модули, простой стиль и запросы GraphQL. Все эти артефакты находятся в нашем src/utils
каталоге.
Вставьте следующие операторы импорта после открывающего тега \<script\>
в GroupChat.vue
.
/* API modules */ import Api from "./utils/api"; import Wss from "./utils/wss"; /* graphQL operations */ import { InitialChatData, CreateUser, DeleteUser, UsersSubscription, CreateMessage, MessagesSubscription, } from "./utils/graphql"; /* Styles */ import "../assets/styles.css";
Данные компонента Vue
Мы можем определить, какие свойства данных мы хотим использовать в функции данных нашего компонента. Все, что нам нужно, - это способ хранить пользователей чата, сообщения, имя «текущего» пользователя и все сообщения, которые еще не были отправлены. Эти свойства можно добавить так:
/* imports ... */ export default { name: "GroupChat", data: () => ({ messages: [], newMessage: "", me: { email: "" }, users: [], }), };
Хуки жизненного цикла Vue
Наши хуки жизненного цикла выполняются в разные моменты «жизни» компонента Vue. Например, когда он установлен или обновлен. В нашем случае нас интересует только то, когда компонент создан и beforeDestroy
. В таких случаях мы хотим либо открыть подписки на чат, либо закрыть подписки на чат.
/* imports ... */ export default { /* other properties ... */ /** * Lifecycle hook executed when the component is created. */ created() { /** * Create Subscription that triggers event when user is * created or deleted */ Wss.subscribe(UsersSubscription, { data: this.handleUser }); /** * Create Subscription that triggers event when * message is created */ Wss.subscribe(MessagesSubscription, { data: this.addMessage }); /** * Fetch initial chat data (Users and last 10 Messages) */ Api.query({ query: InitialChatData }).then(({ data }) => { this.users = data.usersList.items; this.messages = data.messagesList.items; }); /** * Callback executed on page refresh to close chat */ window.onbeforeunload = this.closeChat; }, /** * Lifecycle hook executed before the component is destroyed. */ beforeDestroy() { this.closeChat(); } };
Методы компонента Vue
Мы должны добавить определенные методы, предназначенные для обработки каждого вызова / ответа API (createMessage
, addMessage
, closeChat
и т. Д.). Все они будут сохранены в объекте методов нашего компонента.
Стоит отметить одну вещь: большинство мутаций не ждут и не обрабатывают ответы. Это потому, что у нас есть подписки, которые отслеживают эти мутации. После успешного запуска данные о событиях обрабатываются именно подпиской.
Большинство из этих методов говорят сами за себя. В любом случае, пожалуйста, прочтите комментарии в следующем коде.
/* imports ... */ export default { /* other properties ... */ methods: { /** * Create the new user using a submitted email address. */ createUser() { Api.mutate({ mutation: CreateUser, variables: { email: this.me.email, }, }); }, /** * Delete a user by their ID. */ deleteUser() { Api.mutate({ mutation: DeleteUser, variables: { id: this.me.id }, }); }, /** * Our users subscription listing to both the create * and update events, and therefore we need to choose * the appropriate method to handle the response based * on the mutation type. * * Here, we have an object that looks up the mutation * type by name, returns it and executes the function * while passing the event node. */ handleUser({ data: { Users: { mutation, node }, }, }) { ({ create: this.addUser, delete: this.removeUser, }[mutation](node)); }, /** * Adds a new user to users array, first checking * whether the user being added is the current user. */ addUser(user) { if (this.me.email === user.email) { this.me = user; } this.users.push(user); }, /** * Removes user from the users array by ID. */ removeUser(user) { this.users = this.users.filter( (p) => p.id != user.id ); }, /* Create a new message */ createMessage() { Api.mutate({ mutation: CreateMessage, variables: { id: this.me.id, content: this.newMessage, }, }).then(() => (this.newMessage = "")); }, /** * Our messages subscription only listens to the * create event. Therefore, all we need to do is * push it into our messages array. */ addMessage({ data: { Messages: { node }, }, }) { this.messages.push(node); }, /** * We'll want to close our subscriptions and * delete the user. This method can be * called in our beforeDestroy lifecycle hook and * any other relevantly placed callback. */ closeChat () { /* Close subscriptions before exit */ Wss.close() /* Delete participant */ this.deleteUser(); /* Set me to default */ this.me = { me: { email: '' } } } }, /* lifecycle hooks ... */ }
Шаблон компонента Vue
И последнее, но не менее важное: у нас есть компонент \<template\>
.
Существуют тысячи отличных руководств по созданию красивого пользовательского интерфейса. Это не одно из тех руководств.
Следующий шаблон соответствует минимальным требованиям приложения для группового чата. Вам действительно нужно пойти и сделать это красивым. Тем не менее, давайте быстро пройдемся по ключевой разметке, которую мы здесь реализовали.
Как всегда, читайте комментарии к встроенному коду.
<template> <div id="app"> <!-- Only if the current user has an ID (has been created) should the chat view be rendered. Otherwise, a sign up for is shown. --> <div v-if="me.id" class="chat"> <div class="header"> <!-- Since we're using subscriptions that run in real-time, our number of user currently online will dynamically adjust. --> {{ users.length }} Online Users <!-- A user can leave the chat by executing the closeChat function. --> <button @click="closeChat">Leave Chat</button> </div> <!-- For every message that we're storing in the messages array, we'll render out in a div. Additionally, if the messages participant id matches the current user id, we'll assign it the me class. --> <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> <!-- Our message input is bound to the newMessage data property. --> <div class="input"> <input type="text" placeholder="Say something..." v-model="newMessage" /> <!-- When the user clicks the send button, we run the createMessage function. --> <button @click="createMessage">Send</button> </div> </div> <!-- The sign up flow simply asks the user to enter an email address. Once the input is blurred, the createUser method is executed. --> <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>
Вы не поверите, но теперь весь публичный чат построен. Если вы откроете его в локальной сети (yarn serve
), вы сможете отправлять и получать сообщения. Однако, чтобы доказать, что это настоящий групповой чат, откройте несколько окон и наблюдайте за ходом разговора!
7. Заключение и тестирование
В этом руководстве мы изучили, как использование современных инструментов разработки позволяет нам создавать реальные приложения за считанные минуты.
Надеюсь, вы также узнали, как инициализировать ApolloClient
и SubscriptionClient
для эффективного выполнения запросов GraphQL, изменений и подписок в рабочей области 8base, а также немного о VueJS.
Независимо от того, работаете ли вы над веб-игрой, мобильной игрой, приложениями для обмена сообщениями и уведомлениями или над другими проектами, требующими данных в реальном времени, подписки - отличный инструмент для использования. Мы здесь почти не коснулись!
Платформа 8base позволяет разработчикам создавать потрясающие облачные приложения с использованием JavaScript и GraphQL. Узнайте больше о платформе 8base.
Первоначально опубликовано в блоге 8base.