<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Srushtika Neelakantam</title>
    <description>The latest articles on DEV Community by Srushtika Neelakantam (@srushtika).</description>
    <link>https://dev.to/srushtika</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F92303%2Ff1b8da5f-d387-401b-b4ef-d3218e669470.jpg</url>
      <title>DEV Community: Srushtika Neelakantam</title>
      <link>https://dev.to/srushtika</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/srushtika"/>
    <language>en</language>
    <item>
      <title>Say hello to Ably Chat: A new product optimized for large-scale chat interactions</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Fri, 12 Jul 2024 10:02:11 +0000</pubDate>
      <link>https://dev.to/ably/say-hello-to-ably-chat-a-new-product-optimized-for-large-scale-chat-interactions-1kgp</link>
      <guid>https://dev.to/ably/say-hello-to-ably-chat-a-new-product-optimized-for-large-scale-chat-interactions-1kgp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Today, we're excited to announce the private beta launch of our new chat product! Ably Chat bundles purpose-built APIs for all the chat features your users need in a range of realtime applications, from global livestreams operating at extreme scale to customer support chats embedded within your apps. It is powered by Ably’s reliable global platform with proven performance guarantees and scalability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSeeS6H6qAF1ZI7iZtQVYiC9my00uWBWc-BN-jOM1RGpOuQRUg/viewform" rel="noopener noreferrer"&gt;Request a beta invite now&lt;/a&gt;&lt;/strong&gt; to give it a try!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fas4q0h96l513fsqioagd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fas4q0h96l513fsqioagd.png" alt="Ably chat solutions" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ve had the privilege of working with a wide range of customers including global retailers, CRM vendors, sports franchises, creators, entertainers, and broadcasters - from HubSpot and SportsBet, to 17Live and InvitePeople - providing them with reliable, scalable and low-latency chat. Ably Pub/Sub is already a fantastic fit for a variety of chat use cases. But we’ve been doing a lot of thinking about how we can better help developers to overcome the many challenges of delivering chat features to market quickly, at scale and in an economically viable way.&lt;/p&gt;

&lt;p&gt;That’s why we're excited to kick off the private beta for our new chat product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Ably Chat?
&lt;/h2&gt;

&lt;p&gt;Ably chat is designed to meet a wide range of chat use cases, such as livestreams, in-game communication, customer support, or social interactions in SaaS products. Built on Ably's core service, it abstracts complex details to enable efficient chat architectures.&lt;/p&gt;

&lt;p&gt;Ably Chat comes with purpose-built APIs for a host of chat features enabling you to create 1:1, 1:many, many:1 and many:many chat rooms for any scale; have users send and receive messages; and see the online status of the users in the room or the occupancy i.e. how many people are there in total. You can also build typing indicators, and room level reactions. We are actively working on other features like moderation and the ability to update and interact with chat messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q02Gly140" rel="noopener noreferrer"&gt;Check out the documentation&lt;/a&gt; for a sneak peek of the functionality offered at this stage. If there are other chat features you'd like us to prioritize, &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSdY-b79KBrBy5NOMNkv7nOlvNW7o4twv1aJt1UVLmLFgta5dA/viewform" rel="noopener noreferrer"&gt;please let us know&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Ably Chat looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Connect to a chat room with any number of participants&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basketball-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reactions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reactionsConfig&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;//Subscribe to chat messages&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;displayChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//Subscribe to room reactions&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;reaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;displayReactionAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reaction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//Send a chat message&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;That was such a cool shot!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//Send a room reaction&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;like&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fireworks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;a href="https://hubs.la/Q02Glyl60" rel="noopener noreferrer"&gt;Check out the live demo&lt;/a&gt; to play with it yourself!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ably Chat?
&lt;/h2&gt;

&lt;p&gt;When Ably started in 2016, our goal was to make realtime interactions a key part of digital experiences. Now, we have a mature, battle tested WebSockets platform that gives us the freedom to focus on creating user-centric features that meet your needs in the best way possible. This approach unlocks many benefits for users of Ably Chat, and other products:&lt;/p&gt;

&lt;h3&gt;
  
  
  Composable realtime
&lt;/h3&gt;

&lt;p&gt;A truly great realtime experience often involves combining multiple features together - chat, live updates, collaboration, notifications and more! Unlike chat-specific products or building your own solution from scratch, Ably offers the best of both worlds - full flexibility to build what you want, rapidly.&lt;/p&gt;

&lt;p&gt;Whilst we will keep adding new features into our SDKs we know we’ll never be able to satisfy every unique use-case possible. That’s why we are also evolving our product suite to give you the flexibility to mix and match APIs and create what you need, exactly the way you want to, quickly and efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependable by design
&lt;/h3&gt;

&lt;p&gt;We’ve built a realtime experience platform that ensures predictability of latencies. It is designed to preserve continuity of service at a regional and global level, ensuring capacity and availability. This allows you to maintain varying levels of scale seamlessly. Finally, data integrity comes baked in with message guarantees for ordering and exactly once delivery. This enables you to focus on your application and the user experience, with no infrastructure to build or manage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost optimizations
&lt;/h3&gt;

&lt;p&gt;A realtime platform needs to be able to support reliable chat at extreme scale, but must do so cost effectively. Typically, as chat usage (especially concurrent usage) increases, so does the cost per user. Cost optimizations and affordability of technology have been top of mind for us when designing the roadmap. With the upcoming batching and aggregation features you can maintain a low and stable cost per user. Additionally, Ably’s pricing model has been built for operations at scale with customisations such as hourly billing, usage based pricing and volume discounts.&lt;/p&gt;

&lt;p&gt;All these abstractions and optimizations ultimately mean one thing — you spend less time figuring out the design patterns for good efficiency and a great user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started with Ably Chat
&lt;/h2&gt;

&lt;p&gt;Stay tuned for more updates and features as we roll out this new initiative. &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSeeS6H6qAF1ZI7iZtQVYiC9my00uWBWc-BN-jOM1RGpOuQRUg/viewform" rel="noopener noreferrer"&gt;Sign up for the chat private beta&lt;/a&gt; to access new features early and collaborate on upcoming functionality to shape our roadmap. We're just getting started, and there's plenty more to come!&lt;/p&gt;

</description>
      <category>news</category>
      <category>development</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Collaboration is futile without these UX best-practices</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Mon, 16 Oct 2023 13:44:19 +0000</pubDate>
      <link>https://dev.to/ably/collaboration-is-futile-without-these-ux-best-practices-ef6</link>
      <guid>https://dev.to/ably/collaboration-is-futile-without-these-ux-best-practices-ef6</guid>
      <description>&lt;p&gt;More aspects of our work and life are online than ever before, and the trend towards remote work and increasingly remote teams is set to continue.  Whilst we enjoy the freedom and flexibility that remote work provides,  remote teams also face a significant challenge: establishing (and desiring) a sense of connection despite the physical distance between individuals.&lt;/p&gt;

&lt;p&gt;Many employees express a loss of team spirit and community when they work remotely, which inadvertently impacts productivity. This has led to a &lt;a href="https://hubs.la/Q023dkQt0" rel="noopener noreferrer"&gt;rise in online collaboration tools and services&lt;/a&gt;, which strive to solve this problem by bringing the qualities and benefits of working in person to the virtual workspace. Specifically, they are using collaborative features to deliver the unspoken cues that we have lost in today’s virtual office environments. Meta information in the form of online presence, movement and focus provides a sense of togetherness and helps users to decide what to do next. This enhances not only team-based work - but also team health.&lt;/p&gt;

&lt;p&gt;This article explores the features required to create a great virtual collaborative environment within applications - explaining the purpose of each feature from an end user perspective and the key UX considerations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The essential role collaborative features play in the user experience
&lt;/h2&gt;

&lt;p&gt;So, what are the essential ingredients needed in an application to make users feel they are part of an ‘in-person’ experience? There are two needs to accommodate: the emotional sense of ‘togetherness’, and the sense of being productive.&lt;/p&gt;

&lt;p&gt;From an application perspective this means you need to provide users with three pieces of realtime information: “who is here with me?”, “where are they?” and “what are they doing?”. In a virtual, collaborative setting this information can be provided through the following collaborative features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avatar stack - who is here with me&lt;/li&gt;
&lt;li&gt;Member location - where are they?&lt;/li&gt;
&lt;li&gt;Live cursors - where are they moving to?&lt;/li&gt;
&lt;li&gt;Component locking - what are they doing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s look at each of these in turn, and how they enhance the virtual collaborative experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avatar stack
&lt;/h3&gt;

&lt;p&gt;Avatar stacks show all the users currently collaborating within a shared space. They provide the virtual equivalent of being able to see who is sitting in a room with you. They can help users make informed decisions by indicating the connection state of fellow collaborators, e.g. are they online, offline or have recently left. Avatars can take various forms from photos to initials or symbols.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzre0jto17arbbtvhu9r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzre0jto17arbbtvhu9r.png" alt="avatar-stack-feature"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Member location
&lt;/h3&gt;

&lt;p&gt;Member location is a way to show where fellow collaborators are located within a shared space. Similar to being able to see where people are in a room. This can be useful for understanding what other members are working on, and how their work might affect yours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4ysujj495ygy6n3kx1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4ysujj495ygy6n3kx1q.png" alt="member-location-feature"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Live cursors
&lt;/h3&gt;

&lt;p&gt;Live cursors show the live pointer position of other collaborators. They can be useful to show what someone is currently looking at and their movement around a space. A bit like knowing if someone is looking at the whiteboard, their computer or out the window in a face-to-face session. This is what differentiates the purpose of live cursors from ‘member location’. A member location feature shows ‘what I’m working on’, whilst an animated live cursor shows ‘what I might be looking at’. They can take many forms from the conventional arrow to cursors showing names or avatars.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aqy6jwrlxjb5eveguj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aqy6jwrlxjb5eveguj1.png" alt="live-cursors-feature"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Component locking
&lt;/h3&gt;

&lt;p&gt;Component locking can be a valuable tool for collaborative applications. A lock displayed over a particular component signals to other members that they are currently being edited. Based on that information they can choose to wait or move onto another task.&lt;/p&gt;

&lt;p&gt;Component locking provides much more than a visual cue, however. It helps reduce the instances where users accidentally overwrite each other's work, which needs to be consolidated in the backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1357nmcy5ege7kz1vktf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1357nmcy5ege7kz1vktf.png" alt="component-locking-feature"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  UX design considerations for collaborative features
&lt;/h2&gt;

&lt;p&gt;Having looked at each of the features required to make virtual collaboration as close to in-person as possible, let’s consider the UX requirements for a great collaborative experience, and the features that contribute to it.&lt;/p&gt;

&lt;p&gt;At the end of the day, UX is all about the user and helping them reach their goals. Therefore, any collaborative features should adhere to the following principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep it simple:&lt;/strong&gt; Eliminate visual clutter wherever possible, and cut any unnecessary elements or steps that complicate the user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make it intuitive to use:&lt;/strong&gt; Always consider function first over design. Any features you add should make it as effortless as possible for users to do what they came to do on your site or app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t distract:&lt;/strong&gt; Collaborative features are not meant to be the star of the show, they are meant as subtle visual gestures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, there are a number of best practices to consider on a feature-by-feature basis. Doing so will help ensure you are delivering maximum value to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best practices when building avatar stacks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Think about whether it is practical or beneficial to show a full list of collaborators to the user. For example only showing the avatars for online or recently left members, maybe sufficient. Listing everyone could be overwhelming to users, even degrading the experience.&lt;/li&gt;
&lt;li&gt;In instances where you must show the full list, make sure to order with the online members listed first. This will make it easier for users to see who is available to collaborate, without having to scroll through a long list of users.&lt;/li&gt;
&lt;li&gt;The value of instant updates to online status changes is often underestimated. Avatar stacks are huge contributors to unsaid virtual cues in a shared workspace, so make sure the status updates are reflected immediately in the UI, enabling users to take appropriate action based on that information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxoj3vmx67ac1o6vlhmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxoj3vmx67ac1o6vlhmk.png" alt="avatar-stack-best-practice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Best practices when building a member location feature
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The level of detail that you provide about user location can vary depending on the needs of your users. For example, you might only show the page that a user is on, or you might show the specific component that they are working on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are a variety of ways to display user location, such as using a border matching the member's avatar, the avatar itself, or a textual name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Member location is meant to provide a subtle, visual cue indicating who is working on what. The UI should not distract from the core application or components being worked on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As with avatar stacks, don’t underestimate the importance of instant updates to member location. Make sure the location updates are reflected immediately in the UI so users take appropriate action based on that information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avatar stacks can be used in conjunction with member location to show where exactly someone is within a space - allowing them to jump to their location or follow their movements.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1bmzox69zvv1dctg0g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1bmzox69zvv1dctg0g6.png" alt="member-location-best-practice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Best practices when building live cursors
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understanding the true value of live cursors is important. Make sure they are not a distraction. Consider how many users may need to work synchronously in the same space. Cursors for a few people are fine, but beyond that the UI becomes so busy that any benefits they would provide are gone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The movement of cursors has to be smooth and performant. Jerky movements or having cursors jump around from place to place, will reflect poorly on the entire experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The beauty of live cursors is that it shows the movements of others around you. It is not about watching an animated view of your own cursor. The original cursor will suffice. Not only will this prevent confusion on “who am I again?” but it also means there are no issues like lag to deal with.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When multiple users are active in close proximity it can be difficult to understand who’s who, especially when things are fast moving. In such instances just matching the cursor colour to an avatar is not enough, consider instead the addition of a name and/or avatar to enhance the UX.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure the cursor position is adjusted to the screen size. This will ensure that whatever a user is pointing to is accurately reflected to everyone on different devices - even if that means adjusting to different layouts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Live cursors can extend way beyond pointing and showing movement around a space. Tracking the movement of the cursors also presents the opportunity to:&lt;/p&gt;

&lt;p&gt; - Replay the ‘drag and drop’ animation for UI components.&lt;/p&gt;

&lt;p&gt; - Show mouse interaction, such as clicks, mouse drag etc.&lt;/p&gt;

&lt;p&gt; - Allow users to annotate and draw on the board.&lt;/p&gt;

&lt;p&gt; - Add emotions to a cursor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fou3thns0w8i9gbwn8mlq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fou3thns0w8i9gbwn8mlq.png" alt="live-cursors-best-practice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Best practices for building a component locking feature
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;There may be times where a user forgets they have locked a field or their connection drops. Ensuring the lock status is tied to a member's online status is useful in making sure that no components are locked indefinitely - the APIs in the Spaces SDK do this by default.&lt;/li&gt;
&lt;li&gt;A user may have locked a component but might have stepped away from the system for a long time. It might be a good idea to trigger a dialogue after a certain period of inactivity to check if the user is still there and working on the locked component. Lacking any response, the lock may be automatically released.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30oyapgk2ne9pcd9ukvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30oyapgk2ne9pcd9ukvb.png" alt="component-locking-best-practice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding in-app collaboration
&lt;/h2&gt;

&lt;p&gt;Adding collaborative features, can greatly enhance the end user experience. But there is much to consider, not just from the POV of the user but also the implementation and performance of the features e.g. optimizing for the payload structure, frequency of updates, etc. This often means developer focus is diverted onto figuring out architecting the lower-level detail rather than the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating collaborative environments in a few lines of code
&lt;/h2&gt;

&lt;p&gt;For the reason mentioned above, we came up with a new product “Spaces” — to help developers build collaborative spaces in their apps in just a few lines of code. The Spaces SDK comes with a set of simple, intuitive realtime collaboration APIs for a range of features. With these you can set up a virtual environment, where members can see: a live view of other collaborators working with them; their location within the app, their movement around the space when working on different components, and components actively locked for editing. APIs can be used individually or in combination to create the perfect collaborative space for your users - with minimal effort on your part. We have worked through the architectural challenges of integrating these features, so you can focus on the UX.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://hubs.la/Q0257mf-0" rel="noopener noreferrer"&gt;Spaces&lt;/a&gt; product page and play with our interactive demo or head straight to docs to learn more on the APIs.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>ui</category>
      <category>development</category>
      <category>design</category>
    </item>
    <item>
      <title>Introducing Spaces: Build collaborative environments in a few lines of code</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Mon, 11 Sep 2023 13:39:52 +0000</pubDate>
      <link>https://dev.to/ably/introducing-spaces-build-collaborative-environments-in-a-few-lines-of-code-3n01</link>
      <guid>https://dev.to/ably/introducing-spaces-build-collaborative-environments-in-a-few-lines-of-code-3n01</guid>
      <description>&lt;p&gt;&lt;strong&gt;Spaces comes with a purpose-built SDK to enable developers to add a collaborative environment around existing applications.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are very excited to announce a new product — Spaces! &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.ably.com/spaces" rel="noopener noreferrer"&gt;Spaces SDK&lt;/a&gt; comes with an intuitive set of APIs that allow you to build realtime collaboration features such as avatar stacks, live cursors, member location and component locking, in days. Each API is optimized for their specific use-case, reducing integration effort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faidlg30sqzw8gtpnf365.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faidlg30sqzw8gtpnf365.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Spaces at a glance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feature specific APIs:&lt;/strong&gt; The only product that comes with purpose-built, realtime collaboration APIs for adding avatar stacks, member location, live cursors, and component locking. All APIs are optimized for top performance. For example, the live cursors API automatically batches pointer position events to avoid unnecessary streaming of messages whilst ensuring negligible latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to use:&lt;/strong&gt; Spaces is a simple and intuitive SDK that can be used with any web application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High performing:&lt;/strong&gt; Powered by our market-proven, realtime infrastructure that’s built for reliability at scale, backed by five nines SLAs and messaging guarantees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realtime experiences unlocked:&lt;/strong&gt; Spaces is built to work with Ably’s other complementary products like Pub/Sub Channels to unlock end-to-end realtime messaging throughout your app. While Spaces powers the features you need to enable synchronous collaboration for teams and manage their participant state, Pub/Sub channels allows you to flexibly broadcast and sync app state changes between members, your backend and any other pieces in your system design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enhancing your product with in-app collaboration &amp;amp; Spaces
&lt;/h2&gt;

&lt;p&gt;With the &lt;a href="https://www.ably.com/spaces" rel="noopener noreferrer"&gt;Spaces SDK&lt;/a&gt; you can set up a collaborative environment on any part of your application (or your entire application) so that collaborators have contextual awareness of what everyone is up to. Members of a space can see which other members are online, what they are looking at, their location within the app (this could be a cell, a page, a slide or a folder) and any specific component they’ve locked for editing. All of this and more in just a few lines of code.  No need to change your existing system design or app architecture. No realtime infrastructure to build and maintain to scale the collaboration to millions of virtual spaces and users.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDK feature highlights
&lt;/h2&gt;

&lt;p&gt;Here’s the full set of APIs available in the beta release with many more planned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Space:&lt;/strong&gt; A virtual collaborative space set up on your application where members can collaborate with each other in realtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Members:&lt;/strong&gt; Online users connected to the virtual collaborative space. This powers an &lt;a href="https://ably.com/examples/avatar-stack" rel="noopener noreferrer"&gt;avatar stack&lt;/a&gt; for end users which is a visual representation of a member’s presence - showing them as online and connected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Member location:&lt;/strong&gt; The &lt;a href="https://examples.ably.dev/member-location" rel="noopener noreferrer"&gt;live location&lt;/a&gt; of a user within the app - page, cell, slide, block or anything else that makes sense for your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live cursor:&lt;/strong&gt; The pointer location of members in a virtual space showing what they are looking at. The &lt;a href="https://ably.com/examples/live-cursors" rel="noopener noreferrer"&gt;live cursor API&lt;/a&gt; automatically batches messages and provides the message rate required for smooth performance of 100s of simultaneous cursors (though we don’t recommend going beyond 15 for a good user experience).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component locking:&lt;/strong&gt; Enables end-users to &lt;a href="https://examples.ably.dev/component-locking" rel="noopener noreferrer"&gt;lock specific UI&lt;/a&gt; components while making their edits, so there’s no confusion with other collaborators or disruption to overall collaboration in the space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wojtd9khrgzjc0a9x4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wojtd9khrgzjc0a9x4k.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started today
&lt;/h2&gt;

&lt;p&gt;Spaces is currently available in beta. To get started simply&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign-up for a &lt;a href="https://ably.com/sign-up" rel="noopener noreferrer"&gt;free developer account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Take a closer look at &lt;a href="http://www.ably.com/spaces" rel="noopener noreferrer"&gt;Spaces&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dive into the &lt;a href="https://ably.com/docs/products/spaces" rel="noopener noreferrer"&gt;Spaces docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sign-up for &lt;a href="https://pages.ably.com/collaborative-spaces-webinar" rel="noopener noreferrer"&gt;the webinar&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>news</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>The async/await post we promised</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Wed, 08 Dec 2021 14:49:22 +0000</pubDate>
      <link>https://dev.to/ably/the-asyncawait-post-we-promised-2c50</link>
      <guid>https://dev.to/ably/the-asyncawait-post-we-promised-2c50</guid>
      <description>&lt;p&gt;Great user experience on the web comes from being able to provide users with exactly what they want in the most seamless way possible. Behind the scenes, some user actions may take more time to process than others. For example, showing or hiding an HTML element is a quick operation whereas making an XHR request to get data from an external API is a slower operation. JavaScript provides us with a way to handle them all without giving up that instant delight users naturally expect.&lt;/p&gt;

&lt;p&gt;In this article, we’ll describe how JavaScript executes asynchronous operations and review different ways to write asynchronous code from Callbacks to Promises and explain what’s best and why. Most importantly, we’ll use the modern and recommended way to write asynchronous JavaScript to instantiate and use Ably’s JavaScript SDKs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jump to "&lt;a href="http://link-to-section/"&gt;Using the promise-based version of the &lt;code&gt;ably-js&lt;/code&gt; SDK&lt;/a&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are new to Ably, here's a quick summary - Ably provides APIs to add realtime messaging functionality to your applications. It is based on the &lt;a href="https://ably.com/topic/pub-sub"&gt;Publish/Subscribe&lt;/a&gt; messaging pattern and operates mostly on the &lt;a href="https://ably.com/topic/websockets"&gt;WebSockets&lt;/a&gt; protocol. You can plug in the Ably SDK and start publishing messages in realtime to millions of devices. &lt;a href="https://ably.com/signup"&gt;Sign up&lt;/a&gt; for a free account to explore all the platform's features.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inner workings of JavaScript
&lt;/h2&gt;

&lt;p&gt;JavaScript is a single-threaded programming language. It is predominantly used on the web or in the form of NodeJS in the backend.&lt;/p&gt;

&lt;p&gt;If we focus on the frontend, JavaScript-based applications run in a web browser. The actual execution of the JavaScript code is done by a JavaScript engine, which usually comes in-built with every browser. For example, Google Chrome comes with the popular V8 engine (which is also the engine running NodeJS), Mozilla Firefox comes with the SpiderMonkey engine, Microsoft Edge comes with the Chromium engine, and so on.&lt;/p&gt;

&lt;p&gt;Being single-threaded means that JavaScript can only do one thing at a time and sequentially execute statements in a given piece of code. When the code takes longer to execute, like waiting for some data to return from an external API, the application would essentially halt at that point and the end-user would end up seeing an unresponsive screen.&lt;/p&gt;

&lt;p&gt;But, this doesn’t usually happen, does it?&lt;/p&gt;

&lt;p&gt;The everyday working of frontend JavaScript is made possible not just by the &lt;a href="https://en.wikipedia.org/wiki/JavaScript_engine"&gt;JavaScript engine&lt;/a&gt; provided by the web browser but with a supplement of three key things:&lt;/p&gt;

&lt;p&gt;i) a myriad of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API"&gt;Web APIs&lt;/a&gt;, we'll refer to these as Browser APIs to avoid confusing them with external APIs&lt;/p&gt;

&lt;p&gt;ii) the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#queue"&gt;Message Queue&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;iii) the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#event_loop"&gt;Event Loop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Together these elements allow JavaScript to run asynchronous functions that can continue execution without needing to wait for things that take time.&lt;/p&gt;

&lt;p&gt;Let’s look at how these language and browser features work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  A whirlwind tour of asynchronous JavaScript
&lt;/h2&gt;

&lt;p&gt;In a nutshell, the working of asynchronous JavaScript code can be visualized as shown in the diagram below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1AE6N_3m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/5900152/144869840-33551e3d-49f0-47ee-b7c2-9d7ec300fad2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1AE6N_3m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/5900152/144869840-33551e3d-49f0-47ee-b7c2-9d7ec300fad2.png" alt="js-event-loop" width="880" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The JavaScript engine has a memory heap and a call stack. The memory heap allocates memory for the data in the code and updates the values as per the logic. The call stack is a last in, first out (LIFO) data structure that keeps track of the statement to be executed next to run the code in sequential order.&lt;/p&gt;

&lt;p&gt;What happens when things are slow? Let’s say the call stack encounters a &lt;code&gt;setTimeout()&lt;/code&gt; function. Let's see how the execution of this statement proceeds in the above flow.&lt;/p&gt;

&lt;p&gt;First, we can refer to the only thread that JavaScript has as the "main thread". In case of a &lt;code&gt;setTimeout()&lt;/code&gt;, the main thread will kick off the execution of this statement by calling the function from the Browser APIs but not wait until the execution is complete.&lt;/p&gt;

&lt;p&gt;When the Browser finishes executing the &lt;code&gt;setTimeout()&lt;/code&gt; function, it returns the results. The tricky part, however, is getting these results back to the main thread and showing them in the application.&lt;/p&gt;

&lt;p&gt;These results don't immediately get inserted into the call stack because that would disturb the flow of execution. Instead, it inserts the results at the end of the Message Queue. The event loop will then follow a process to decide the best time to pick this up and insert it into the call stack.&lt;/p&gt;

&lt;p&gt;The best resource I’ve come across to understand the JavaScript event loop is this amazing talk by Philip Roberts - &lt;a href="https://youtu.be/8aGhZQkoFbQ"&gt;What the heck is the event loop anyway?&lt;/a&gt;. While I’ve summarized the explanation below I’d still recommend giving it a watch.&lt;/p&gt;

&lt;p&gt;The Event loop is essentially an infinite &lt;code&gt;while&lt;/code&gt; loop (hence the name) that continuously checks for two things:&lt;/p&gt;

&lt;p&gt;i) if the call stack is empty&lt;/p&gt;

&lt;p&gt;ii) if there are any elements in the Message Queue&lt;/p&gt;

&lt;p&gt;When both these conditions become true, the event loop picks up the first element in the queue and puts it on the call stack for the main thread to execute it.&lt;/p&gt;

&lt;p&gt;The interesting thing to consider here is how we can let the runtime know that a certain statement depends on an external resource (where the processing is being done somewhere else) and may take time to return. We want the execution to continue, not pause while waiting on that external function to complete. Let's explore this next.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Callbacks to Promises to Async/Await
&lt;/h2&gt;

&lt;p&gt;We can think of any asynchronous operation we perform in JavaScript as an API call. This call is done either to an in-built API provided by the browser, for example, &lt;code&gt;setTimeout()&lt;/code&gt;, or to an API from a third-party provider, for example &lt;code&gt;ably.channel.publish()&lt;/code&gt;. In fact, this call can also be done just to another function that's part of the same application but let's assume it is an external function for a better understanding. I've linked some code examples of native async functions in the 'Further reading' section at the end.&lt;/p&gt;

&lt;p&gt;The implementation of an async function provided by the API needs to have a way to tell the main thread what needs to be done when it has finished executing the time taking task.&lt;/p&gt;

&lt;p&gt;This can be done in one of the following three ways:&lt;/p&gt;

&lt;p&gt;i) Callbacks&lt;/p&gt;

&lt;p&gt;ii) Promises with .then syntax&lt;/p&gt;

&lt;p&gt;iii) Promises with async/await syntax&lt;/p&gt;

&lt;p&gt;Let’s explore them one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1 - Async JavaScript with callbacks
&lt;/h3&gt;

&lt;p&gt;A callback is a function that is passed to another function as a parameter. When calling the async function initially, we provide it with a callback function as one of the parameters. When the async function finishes execution, it calls that callback function, along with the results of the execution as arguments. At this point, the callback function is placed on the Message Queue and will eventually be picked up by the event loop and dropped into the call stack for the main thread to execute it.&lt;/p&gt;

&lt;p&gt;Let’s take a look at an example with the asynchronous channel publish function provided by Ably’s JavaScript SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 1 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* function reference:
publish(String name, Object data, callback(**ErrorInfo** err))
*/&lt;/span&gt;

&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hey there! What is up?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the last (optional) parameter in the publish function expects a callback function.&lt;/p&gt;

&lt;p&gt;From the Ably SDK (i.e. the async function provider) side of things, when the publish function is called, it executes that function logic. When it’s done, it calls the callback function and passes it some data if it's applicable. This would look something like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 2 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;RealtimeChannel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* do some stuff to execute the async operation */&lt;/span&gt;

    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As explained before, this callback function will be put at the end of the Message Queue. This will be picked up by the event loop and put onto the call stack which is when it’ll be executed by the main thread. At this point, it'll print the success message to the console depending on the value of the error parameter passed to it.&lt;/p&gt;

&lt;p&gt;Ok, that's all well and good. We've understood a way to write asynchronous JavaScript functions, so why even consider other options?&lt;/p&gt;

&lt;p&gt;Callbacks are a simple concept and work well for standalone asynchronous operations. However, they can quickly get tedious to write and manage if they have dependencies on each other. For example, consider a scenario where you need to do certain async things sequentially, using the data from one task in the other, say:&lt;/p&gt;

&lt;p&gt;i) enter presence on a channel&lt;/p&gt;

&lt;p&gt;ii) get some historical messages&lt;/p&gt;

&lt;p&gt;iii) publish a new message on the channel with the first message retrieved from history&lt;/p&gt;

&lt;p&gt;The callback-based implementation for this scenario will look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 3 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* function references:
- enter(Object data, callback(ErrorInfo err))
- history(Object options, callback(ErrorInfo err, PaginatedResult&amp;lt;Message&amp;gt; resultPage))
- publish(String name, Object data, callback(**ErrorInfo** err))
*/&lt;/span&gt;

&lt;span class="c1"&gt;// step 1 - enter presence&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client has successfully entered presence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// step 2 - get historical messages after presence enter&lt;/span&gt;
  &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstHistoryMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// step 3 - publish a new message after get history&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Hey there! What is up?, my first history msg was &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstHistoryMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this is an accurate implementation and will work perfectly fine, it already looks messy and difficult to manage due to the multiple nested callbacks. This is commonly referred to as &lt;a href="http://callbackhell.com/"&gt;Callback Hell&lt;/a&gt; because debugging or maintaining anything which looks like this would be a daunting task. And, this is exactly the reason we have other, more modern ways of writing asynchronous JavaScript functions. Let's explore these next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2 - Async JavaScript with Promises (.then syntax)
&lt;/h3&gt;

&lt;p&gt;The second option introduces a concept called ‘&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"&gt;Promises&lt;/a&gt;’. Instead of calling a callback function, the API side implementation of the asynchronous function will create and return a "promise" to the requesting client that wants to execute the async function.&lt;/p&gt;

&lt;p&gt;A Promise can have one of the following three states:&lt;/p&gt;

&lt;p&gt;i) &lt;strong&gt;Pending&lt;/strong&gt; - meaning we’ve started an async operation but its execution has not completed yet&lt;/p&gt;

&lt;p&gt;ii) &lt;strong&gt;Resolved&lt;/strong&gt; (or Fulfilled) - meaning we started an async task and it has finished successfully&lt;/p&gt;

&lt;p&gt;iii) &lt;strong&gt;Rejected&lt;/strong&gt; - meaning we started an async task but it finished unsuccessfully, in most cases with a specific error that will be returned to the client&lt;/p&gt;

&lt;p&gt;Let's consider a Promise based async operation and again see both sides of the coin i.e. what happens on the API side implementation as well as the requesting client side. This time, let's first take a look at the API side of things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 4 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;RealtimeChannel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;publish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="cm"&gt;/*
      do some stuff to execute the async operation
      */&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The promise executor in the API calls the &lt;code&gt;resolve()&lt;/code&gt; function if the async task was executed as expected, along with the results of the operation. However, if there was some issue with the execution it calls the &lt;code&gt;reject()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;A requesting client can consume such a &lt;code&gt;Promise&lt;/code&gt; using a &lt;code&gt;.then()&lt;/code&gt; function attached to the async function call. The &lt;code&gt;.then()&lt;/code&gt; code block is similar to a callback code block and will be executed when the async task has finished executing. We can also attach a &lt;code&gt;.catch()&lt;/code&gt; to the &lt;code&gt;.then()&lt;/code&gt; block to catch any errors that may have occurred during the execution of the async task.&lt;/p&gt;

&lt;p&gt;In terms of the explanation above, the &lt;code&gt;.then()&lt;/code&gt; block will be executed when the promise executor in the API calls the &lt;code&gt;resolve()&lt;/code&gt; function and the &lt;code&gt;.catch()&lt;/code&gt; block will be executed when the API calls the &lt;code&gt;reject()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;At the time of writing this article, the Ably JS SDK doesn't provide promises by default. To be able to use the promise version of the SDK, we need to use &lt;code&gt;new Ably.Realtime.Promise()&lt;/code&gt; constructor when instantiating the library.&lt;/p&gt;

&lt;p&gt;Let's now see how our example will work on the client side&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 5 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cm"&gt;/* function reference:
publish(String name, Object data): Promise&amp;lt;void&amp;gt;
*/&lt;/span&gt;

&lt;span class="nx"&gt;channel&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hey there! What is up?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;There was an error while publishing: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you compare the above with the "Code Snippet 1", it seems more logical in the sense that we can understand that certain statements will execute after certain other statements due to the literal English meaning of the word 'then'.&lt;/p&gt;

&lt;p&gt;The real advantage however can be seen if we need to perform multiple asynchronous tasks sequentially, in some cases using the data returned in the previous async task.&lt;/p&gt;

&lt;p&gt;Let's consider the same scenario as we did in the callbacks version:&lt;/p&gt;

&lt;p&gt;i) enter presence on a channel&lt;/p&gt;

&lt;p&gt;ii) get some historical messages&lt;/p&gt;

&lt;p&gt;iii) publish a new message on the channel with the first message retrieved from history&lt;/p&gt;

&lt;p&gt;Let's see how this will look like using Promises with a &lt;code&gt;.then&lt;/code&gt; syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 6 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* function references:
- enter(Object data): Promise&amp;lt;void&amp;gt;
- history(Object options): Promise&amp;lt;PaginatedResult&amp;lt;Message&amp;gt;&amp;gt;
- publish(String name, Object data): Promise&amp;lt;void&amp;gt;
*/&lt;/span&gt;

&lt;span class="c1"&gt;// step 1 - enter presence&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this block executes after the presence enter is done&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client has successfully entered presence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//step 2 - get historical messages&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this block executes after the channel history is retrieved&lt;/span&gt;
    &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstHistoryMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;//step 3 - publish a new message&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Hey there! What is up?, my first history msg was &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstHistoryMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this block executes after the message publish is done&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this block executes if there's an error in any of the blocks in this Promise chain&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We have an error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the Promise version with a &lt;code&gt;.then()&lt;/code&gt; syntax reduces the complexity and the level of indentation when compared to the callbacks approach. This helps us understand and maintain the code much easily.&lt;/p&gt;

&lt;p&gt;However, as you can see with this option, we need to wrap each execution step in a function call and return the results to the next &lt;code&gt;.then()&lt;/code&gt;. Although a huge improvement from the callbacks syntax, it seems like it could still get verbose pretty quickly. This is what the async/await syntax helps us with. Let's understand that next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3 - Async JavaScript with Promises (async/await syntax)
&lt;/h3&gt;

&lt;p&gt;This third option is just another version of the second option. There's no change on the API side of things. The API would still create a 'Promise' and either &lt;code&gt;resolve()&lt;/code&gt; or &lt;code&gt;reject()&lt;/code&gt; it after the async task is executed.&lt;/p&gt;

&lt;p&gt;The way we consume it on the front end, however, is different (and better!). The async/await provides syntactic sugar to reduce the complexity in chained async tasks. Let's take a look at how the "Code Snippet 6" above would look like if we use async/await instead of &lt;code&gt;.then()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 7 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* function references:
- enter(Object data): Promise&amp;lt;void&amp;gt;
- history(Object options): Promise&amp;lt;PaginatedResult&amp;lt;Message&amp;gt;&amp;gt;
- publish(String name, Object data): Promise&amp;lt;void&amp;gt;
*/&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// step 1 - enter presence&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client has successfully entered presence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//step 2 - get historical messages&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messagesPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Retrieved history successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstHistoryMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;//step 3 - publish a new message&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Hey there! What is up?, my first history msg was &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstHistoryMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We have an error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you may have observed, we've wrapped all our statements in a function this time. This is because the async/await syntax can only be used in functions starting with the &lt;code&gt;async&lt;/code&gt; keyword. Such an async function can then contain zero or more &lt;code&gt;await&lt;/code&gt; statements.&lt;/p&gt;

&lt;p&gt;Statements that begin with the keyword &lt;code&gt;await&lt;/code&gt; are asynchronous functions. Similar to the previous option with Promises using the &lt;code&gt;.then()&lt;/code&gt; syntax, these statements get returned via the Message Queue when the underlying Promise provided by the API calls either a &lt;code&gt;reject()&lt;/code&gt; or a &lt;code&gt;resolve()&lt;/code&gt; function.&lt;/p&gt;

&lt;h4&gt;
  
  
  Concurrency of independent asynchronous statements
&lt;/h4&gt;

&lt;p&gt;Given that the async/await approach looks a lot like writing synchronous statements, it is a common mistake to make independent code unnecessarily wait for the previous tasks to finish instead of having them execute concurrently (in parallel). For example, in the code examples we saw in the previous sections, if entering the client in the presence set, retrieving history and publishing a new message had no dependencies on each other, we can easily do these things in parallel instead of sequentially.&lt;/p&gt;

&lt;p&gt;This can be done using the &lt;code&gt;Promise.all()&lt;/code&gt; function as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 8 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* function references:
- enter(Object data): Promise&amp;lt;void&amp;gt;
- history(Object options): Promise&amp;lt;PaginatedResult&amp;lt;Message&amp;gt;&amp;gt;
- publish(String name, Object data): Promise&amp;lt;void&amp;gt;
*/&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enterPresence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getHistoryMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hey there! What is up?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// perform all three async functions concurrently&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;enterPresence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getHistoryMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publishMessage&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client has successfully entered presence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Retrieved history successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messagesPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;messagesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`History message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We have an error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cm"&gt;/*
Note the publish function doesn't use any data returned 
by the History API in this case as we are considering the three functions
to be executed independently of each other.
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The case of asynchronous event listeners
&lt;/h3&gt;

&lt;p&gt;By now, we have a good understanding that Promises with either the &lt;code&gt;.then()&lt;/code&gt; or the &lt;code&gt;async/await&lt;/code&gt; syntax are a big improvement over callbacks. But what happens in the case of asynchronous event listeners where you are constantly listening for some updates. For example, in case of a &lt;code&gt;setInterval()&lt;/code&gt; from the inbuilt Browser APIs or &lt;code&gt;ably.channel.subscribe()&lt;/code&gt; from the Ably API?&lt;/p&gt;

&lt;p&gt;Promises are great for one off execution of an async task that either resolves or rejects based on some logic. However, in the case of a subscription, we'd need the resolution to happen multiple times i.e. whenever there's a new message to be pushed from the API to the listening client. Promises unfortunately cannot do that and can resolve only once. So, for active listeners that return data repeatedly, it's better to stick with callbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the promise-based version of the &lt;code&gt;ably-js&lt;/code&gt; SDK
&lt;/h2&gt;

&lt;p&gt;As per the examples we've been seeing so far, it is clear that Ably’s JavaScript SDK provides a promisified version. This means we can consume the asynchronous functions (except for listeners) using the async/await syntax. In the devrel team, we've been using the async style API in our latest demo - the &lt;a href="https://github.com/ably-labs/fully-featured-scalable-chat-app"&gt;Fully Featured Scalable Chat app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the time of writing this article, the default way to consume async functions using the Ably JS SDK is using callbacks, but in this section, we’ll take a look at a few key functions where we consume the promisified API using the async/await syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Importing and instantiating the Ably Realtime or Rest instances:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 9 */&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//before - instantiating the Ably SDKs, callback version&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//now - instantiating the Ably SDKs, Promise version&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Attaching to a channel&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 10 */&lt;/span&gt;

&lt;span class="c1"&gt;//before - attaching to a channel, callback version&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;channel attached&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//now - attaching to a channel, promise with async/await version&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;attachChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;attachChannel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Retrieving and updating presence status on a channel&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 11 */&lt;/span&gt;

&lt;span class="c1"&gt;//before - presence functions, callback version&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Presence members are: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client entered presence set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client presence status updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Client left presence set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//now - presence functions, promise with async/await version&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ablyPresenceStuff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ablyPresenceStuff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cm"&gt;/*
Please note - the above code snippets are slightly 
different in terms of how they'd run.

The callback version concurrently executes all four functions,
whereas the async/await version executes all the statements 
sequentially.

Please scroll back up and read 
'**Concurrency of independent asynchronous statements'** 
if you are interested to learn more about this behaviour.
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Publishing messages&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 12 */&lt;/span&gt;

&lt;span class="c1"&gt;//before - publishing messages, callback version&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hey, this is event data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Publish done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//now - publishing messages, Promise with async/await version&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;publishToAbly&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hey, this is event data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Publish done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;publishToAbly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Subscribing to messages&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 13 */&lt;/span&gt;

&lt;span class="c1"&gt;//before - subscribing to messages, callback version&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New message received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//now - subscribing to messages, Promise with async/await version&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New message received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/*
Please note, there's no change here. As described in the previous section 
Promises cannot be used with listeners which need be triggered multiple times.

Hence, in this case, we stick to callbacks.
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Retrieving historical messages&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Code Snippet 14 */&lt;/span&gt;

&lt;span class="c1"&gt;//before - history API, callback version&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resultPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resultPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//now - history API, callback version&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getAblyHistory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;resultPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;getAblyHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We are moving to Promise by default
&lt;/h2&gt;

&lt;p&gt;In the upcoming versions of the JS SDK, you won't need to instantiate the promise version explicitly with &lt;code&gt;Ably.Realtime.Promise(options)&lt;/code&gt;. Instead, if you do &lt;code&gt;Ably.Realtime(options)&lt;/code&gt;. It'll use the promisified API by default.&lt;/p&gt;

&lt;p&gt;If you want to stick to using the callbacks version at that point, you can explicitly instantiate the Callbacks constructor with &lt;code&gt;Ably.Realtime.Callbacks(options)&lt;/code&gt; and continue using callbacks as default.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/Srushtika/c0a19853af6ceb40c155dc8c2bac292b"&gt;Sample code snippets showing callbacks and promises using native functions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Working examples of using the async style with the Ably APIs:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@ably-labs/react-hooks"&gt;Ably React Hooks NPM package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ably-labs/fully-featured-scalable-chat-app"&gt;A fully featured chat demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/ably/ably-js"&gt;Ably JavaScript SDK&lt;/a&gt; repository&lt;/li&gt;
&lt;li&gt;Realtime use-case demos on &lt;a href="https://github.com/ably-labs"&gt;Ably Labs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>codenewbie</category>
      <category>100daysofcode</category>
    </item>
    <item>
      <title>Free DevRel mentorship</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Fri, 19 Nov 2021 12:08:50 +0000</pubDate>
      <link>https://dev.to/srushtika/free-devrel-mentorship-22b8</link>
      <guid>https://dev.to/srushtika/free-devrel-mentorship-22b8</guid>
      <description>&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--OzZvFIUC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1325901823849402371/IsBgEhEm_normal.jpg" alt="Srushtika Neelakantam profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Srushtika Neelakantam
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/srushtika"&gt;@srushtika&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Hey folks! I'm offering some free mentorship sessions for anyone trying to get into DevRel or someone who recently started in this role and is trying to figure things out 🙌&lt;br&gt;&lt;br&gt;(Details 🧵👇)&lt;br&gt;&lt;br&gt;&lt;a href="https://twitter.com/hashtag/devrel"&gt;#devrel&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/developerrelations"&gt;#developerrelations&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      11:21 AM - 19 Nov 2021
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1461655980467531777" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1461655980467531777" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1461655980467531777" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;



&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--OzZvFIUC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1325901823849402371/IsBgEhEm_normal.jpg" alt="Srushtika Neelakantam profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Srushtika Neelakantam
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/srushtika"&gt;@srushtika&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      What is the timeline/schedule/bandwidth?&lt;br&gt;&lt;br&gt;I have the bandwidth currently to mentor 3 people until Feb 2022 at least. After this period we'll review any time constraints on either side and decide the best ways to move forward. I'm in GMT and can do catchups between 9 - 5
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      11:21 AM - 19 Nov 2021
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1461655981893599236" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1461655981893599236" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1461655981893599236" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;



&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--OzZvFIUC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1325901823849402371/IsBgEhEm_normal.jpg" alt="Srushtika Neelakantam profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Srushtika Neelakantam
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/srushtika"&gt;@srushtika&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      How will it work?&lt;br&gt;&lt;br&gt;We'll establish a specific goal and try to work towards reaching that in the specified time. Along the way, we'll have regular calls or textual catchups to decide on the next steps or review previous ones.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      11:21 AM - 19 Nov 2021
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1461655983147597836" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1461655983147597836" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1461655983147597836" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;



&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--OzZvFIUC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1325901823849402371/IsBgEhEm_normal.jpg" alt="Srushtika Neelakantam profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Srushtika Neelakantam
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/srushtika"&gt;@srushtika&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      How can I reach out?&lt;br&gt;&lt;br&gt;Please send me a DM on Twitter with some details around:&lt;br&gt;- why are you seeking this mentorship?&lt;br&gt;- very vaguely what would you like to achieve during this?&lt;br&gt;- anything else you'd like to say or questions you might have
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      11:21 AM - 19 Nov 2021
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1461655984393359361" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1461655984393359361" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1461655984393359361" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


</description>
      <category>devrel</category>
      <category>beginners</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Database-driven realtime architectures: building a serverless and editable chat app - Part 2</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Fri, 08 Oct 2021 12:30:49 +0000</pubDate>
      <link>https://dev.to/ably/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-2-2lg1</link>
      <guid>https://dev.to/ably/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-2-2lg1</guid>
      <description>&lt;p&gt;Hello again!&lt;/p&gt;

&lt;p&gt;Welcome to Part 2 of this article series where we go through database-driven architectures by understanding the nitty gritty of a chat app where you can edit messages.&lt;/p&gt;




&lt;p&gt;If you missed the Part 1 of this article series, you can jump to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/ablydev/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-1-1ceh"&gt;Database-driven realtime architectures: building a serverless and editable chat app - Part 1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, check out the &lt;a href="https://serverless-scalable-chat.netlify.app/"&gt;editable chat app demo&lt;/a&gt; or explore the &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo"&gt;project on GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;In the previous part, we explored the high-level architecture of the chat app and understood how we made it possible to implement database-driven realtime messaging using the Ably-Postgres connector.&lt;/p&gt;

&lt;p&gt;In this part, we will explore each section of that architecture and focus on implementation details to get the app working. It is divided into the following sections and topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Section 1: Navigating the Nuxt app (even if you are not a Nuxt developer)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Section 2: PostgresDB setup for our chat app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Section 3: Lambda function setup on the Ably integrations dashboard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Section 4: Set up the Ably Postgres connector for the chat app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recap of the app architecture &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dive right in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 1 - Navigating the Nuxt app (even if you are not a Nuxt developer)
&lt;/h2&gt;

&lt;p&gt;Let's start with the frontend app written in NuxtJS. The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app"&gt;chat-web-app&lt;/a&gt; folder in the GitHub repo contains the Nuxt app. The chat app is a static site that can be hosted on any CDN without needing an origin server. This is a classic example of &lt;a href="https://jamstack.org/generators/nuxt/"&gt;Jamstack architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uNegULZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w14as5pxq21s14728iis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uNegULZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w14as5pxq21s14728iis.png" alt="Section 1 of the architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have never worked with Nuxt, here are the basic things you need in order to understand the web application and, more importantly, the data flow within the app and external services. In this case, this is just Ably.&lt;/p&gt;

&lt;h3&gt;
  
  
  Folder structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/assets"&gt;assets folder&lt;/a&gt; contains any uncompiled assets we need in the app, such as various images for the tech stack logos and architecture diagrams.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/components"&gt;components folder&lt;/a&gt; contains all the Vue components needed in the web app. A component is a reusable Vue instance. Each component has its own HTML, CSS, and JavaScript. This makes it easy to plug the visual component into as many places as needed without needing to repeat the code. We are also using Tailwind CSS framework to easily add various CSS classes. You can find these classes in their documentation but the key thing to note here is that we use the &lt;code&gt;@apply&lt;/code&gt; directive to apply these classes to various HTML elements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/netlify/functions"&gt;netlify/functions&lt;/a&gt; folder contains the logic for enabling an authentication endpoint for Ably. This is a format expected by Netlify to be able to automatically recognize each file in there as an executable serverless function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/pages"&gt;pages&lt;/a&gt; folder contains Vue components representing a specific route in the web app. Nuxt automatically creates routes for any files added within this folder. So &lt;code&gt;index.vue&lt;/code&gt; would represent &lt;code&gt;/&lt;/code&gt; and as an example &lt;code&gt;products.vue&lt;/code&gt; would map to the &lt;code&gt;/products&lt;/code&gt; route.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/static"&gt;static folder&lt;/a&gt; contains assets that don't need to be compiled and can be served as is. We just have a favicon here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/store"&gt;store folder&lt;/a&gt; contains files related to the VueX store. VueX is Vue's state management library. It enables us to centrally manage the state of the app. This not only allows us to separate out visual components from the data flow within the app, but also allows us to easily extend or reuse any methods. VueX also provides a very structured way to manage state which allows for easy debugging and monitoring. Nuxt comes with VueX out of the box, so no need to install it separately. However, it is disabled until we add any files in the store folder. We'll look at the store extensively in the following sections.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/blob/main/chat-web-app/nuxt.config.js"&gt;nuxt.config.js file&lt;/a&gt; contains configuration for various things in our Nuxt app. This is the place to include any HTML headers, metadata, etc. It also contains modules that are extensions to Nuxt's core functionality. We are mainly using &lt;a href="https://tailwindcss.com/"&gt;tailwind&lt;/a&gt; (a CSS framework), &lt;a href="https://fontawesome.com/"&gt;fontawesome&lt;/a&gt; (library to add icons), and &lt;a href="https://www.npmjs.com/package/dotenv"&gt;dotenv&lt;/a&gt; (library to allow using environment variables) as modules in our app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/blob/main/chat-web-app/tailwind.config.js"&gt;tailwind.config.js file&lt;/a&gt; is the default config file added by Tailwind which can be used to add any customizations. We don't have any at the moment so this file is mostly empty.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Vue components
&lt;/h3&gt;

&lt;p&gt;Here's a screenshot of the chat app with the visual components labelled as they appear in the repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ALqA3pNU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4eq2db5fsmkkv5sjrhud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ALqA3pNU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4eq2db5fsmkkv5sjrhud.png" alt="Vue components"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two components missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;UsernameInput.vue&lt;/code&gt;: a username input box that appears before you enter the chat&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PresenceListMobile.vue&lt;/code&gt;: the mobile version of the presence list which is hidden by default.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The starting point for all these components is the &lt;code&gt;index.vue&lt;/code&gt; file in the &lt;code&gt;pages&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;All Vue components have three sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HTML: everything between the &lt;code&gt;&amp;lt;template&amp;gt;&amp;lt;/template&amp;gt;&lt;/code&gt; tags&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Javascript: everything between the &lt;code&gt;&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tags&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS: everything between the &lt;code&gt;&amp;lt;style&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt; tags&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JavaScript section exports a default object with various data variables, watchers, and methods. It also has imports at the top as needed. The following is a noteworthy import statement because it allows an easy way to work with the data in the store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mapGetters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mapMutations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mapActions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vuex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is an easy way to access various elements in the global store of our app and manipulate the state of the web app. We'll learn more about what these various things mean in the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  VueX state management for the chat app
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;index.js&lt;/code&gt; file in the &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/tree/main/chat-web-app/store"&gt;store folder&lt;/a&gt; is the starting point of the state management in our application. It exports a new store after instantiating a VueX store with four objects: state, getters, mutations, and actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State&lt;/strong&gt;: This single object contains the application-level state that represents the single source of truth and keeps components n sync with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getters&lt;/strong&gt;: Getters are methods to compute derived states for use anywhere in the app. In the chat app, all the getters return the state object as is, except for &lt;code&gt;getIsUsernameEntered&lt;/code&gt; which returns a boolean depending on whether the username was entered or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutations&lt;/strong&gt;: Mutations are methods that change the value of a certain state object. They should always be synchronous to ensure that we have a good view of the state changes. To update the state based on an asynchronous operation, we use "actions" as described next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actions&lt;/strong&gt;: We use actions to perform asynchronous operations and call a mutation when ready to change the state as a result of that async operation. This is the most important part of the store in our app as this is where we connect to Ably and publish and subscribe to messages on various channels. &lt;/p&gt;

&lt;h3&gt;
  
  
  Inside actions.js
&lt;/h3&gt;

&lt;p&gt;Before adding any methods to be exported out of this file, I've imported the Ably npm library with &lt;code&gt;import * as Ably from "ably";&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's now take a look at each method:&lt;/p&gt;

&lt;p&gt;1. The &lt;code&gt;instantiateAbly()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// init an Ably realtime instance using token auth&lt;/span&gt;
&lt;span class="nx"&gt;instantiateAbly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ablyInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://serverless-scalable-chat.netlify.app/.netlify/functions/ably-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;echoMessages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// when ably is successfully connected, set state variables and call methods to attach to various channels and subscribe to the presence set&lt;/span&gt;
  &lt;span class="nx"&gt;ablyInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setAblyClientId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ablyInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setAblyConnectionStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setAblyRealtimeInstance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ablyInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;initAblyChannels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subscribeToAblyPresence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, the Ably Realtime library is instantiated with two parameters, called &lt;a href="https://ably.com/documentation/realtime/usage#client-options"&gt;client options&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;authUrl&lt;/code&gt;: this is the URL of the authentication endpoint. The app uses Token-based authentication and Netlify serverless functions to issue Token Requests to any legitimate frontend clients that'd like to authenticate with Ably and use its service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;echoMessages&lt;/code&gt;: By default, any messages published to Ably are also echoed back on the same connection. To override the default, set it to false.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once Ably is successfully connected, some commit methods are called to save the data into variables. The commit method calls a mutation, which in turn updates the state of the app. The dispatch method is used to invoke other actions, in this case &lt;code&gt;initAblyChannels&lt;/code&gt; and &lt;code&gt;subscribeToAblyPresence&lt;/code&gt;. We'll see these next.&lt;/p&gt;

&lt;p&gt;2. The &lt;code&gt;initAblyChannels()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// attach to the incoming and outgoing channels&lt;/span&gt;
&lt;span class="nx"&gt;initAblyChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outgoingCh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incomingCh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incomingChat&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setAblyChannelInstances&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;outgoingCh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;incomingCh&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subscribeToChannels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, we instantiate both our Ably channels, one for publishing messages into, (outgoing chat) and the other for subscribing to messages ( incoming chat channel). We then commit the channel instances and call a new method to subscribe to channels.&lt;/p&gt;

&lt;p&gt;3. The &lt;code&gt;subscribeToChannels()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// subscribe to the incoming and outgoing channel instances&lt;/span&gt;
&lt;span class="nx"&gt;subscribeToChannels&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incomingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;operationPerformed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/* check if the update is about a new message being inserted or an existing message being edited */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;operationPerformed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// set the update type to new, so we can scroll the message list to bottom&lt;/span&gt;
      &lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setChatMsgArrayUpdateType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMessagesArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;operationPerformed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// set the update type to edit, find and update the array object with new data&lt;/span&gt;
      &lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setChatMsgArrayUpdateType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;msgObjToEdit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMessagesArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msg_id&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;msgObjToEdit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msg_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msg_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;msgObjToEdit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_edited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_edited&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, we subscribe to the incoming-chat channel instances from the previous method. When the subscription callback is triggered, we check if the message received is &lt;code&gt;INSERT&lt;/code&gt; or &lt;code&gt;UPDATE&lt;/code&gt;. The former would mean a new chat message was published (and thus inserted as a new row into the database). The latter would mean an existing chat message was edited (hence a row was updated in the database).&lt;/p&gt;

&lt;p&gt;If it's a new message, we append it to the chat messages array. If it's an updated message, we find the message in the chat messages array using its unique &lt;code&gt;msg_id&lt;/code&gt; property, and update the &lt;code&gt;is_Edited&lt;/code&gt; property to true.&lt;/p&gt;

&lt;p&gt;We also call a mutation to set the &lt;code&gt;chatMsgArrayUpdateType&lt;/code&gt;. This ensures that the chat list scrolls to the bottom whenever a new message arrives but not when a message is edited.&lt;/p&gt;

&lt;p&gt; 4. The &lt;code&gt;subscribeToAblyPresence()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//subscribe to Ably presence updates on the outgoing channel&lt;/span&gt;
&lt;span class="nx"&gt;subscribeToAblyPresence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Entered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handleNewMemberEntered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;leave&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handleExistingMemberLeft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// get a list of members already present in the Ably presence list&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;presenceList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;presenceList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handleNewMemberEntered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;presenceList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, we subscribe to &lt;code&gt;enter&lt;/code&gt; and &lt;code&gt;leave&lt;/code&gt; updates on the outgoing chat channel and call new methods to handle entries and leaves as they happen. We also use &lt;code&gt;presence.get&lt;/code&gt; to get a list of existing members on the channel. This is useful to retrieve a list of people who came online before the current client and are still connected. We update the onlineMembersArray via mutations in each case. We also keep a local count of the number of people present using the &lt;code&gt;presenceCount&lt;/code&gt; state variable, and update it whenever someone leaves or joins also via mutations (invoked using the commit keyword from the methods in the "actions" object).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// handle a new member entering the Ably presence set&lt;/span&gt;
&lt;span class="nx"&gt;handleNewMemberEntered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setPresenceIncrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setOnlineMembersArrayInsert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="c1"&gt;// handle an existing member entering the Ably presence set&lt;/span&gt;
&lt;span class="nx"&gt;handleExistingMemberLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setOnlineMembersArrayRemove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setPresenceDecrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; 5. The &lt;code&gt;enterClientInAblyPresenceSet()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// enter the current client in the Ably presence set&lt;/span&gt;
&lt;span class="nx"&gt;enterClientInAblyPresenceSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, we enter the current client into the presence set by calling the &lt;code&gt;presence.enter()&lt;/code&gt; method on the outgoing chat instance. Doing this will invoke &lt;code&gt;presence.subscribe('enter')&lt;/code&gt; for this and all the other clients. This is done when the user types in a username and clicks on the &lt;code&gt;enter chat&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt; 6. The &lt;code&gt;publishMyChatMsgToAbly()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// global method to publish a chat message on the outgoing channel instance&lt;/span&gt;
&lt;span class="nx"&gt;publishMyChatMsgToAbly&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;chatMsg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chatMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatMsg&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use this method to publish a new chat message to Ably. This is invoked from the &lt;code&gt;ChatInput&lt;/code&gt; component. This is included as part of the global state management methods to allow for reusability and publishing of messages to Ably from any component in our UI.&lt;/p&gt;

&lt;p&gt; 7. The &lt;code&gt;publishMyEditedMsgToAbly()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// global method to publish an edit update on the outgoing channel instance&lt;/span&gt;
&lt;span class="nx"&gt;publishMyEditedMsgToAbly&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;editedMsg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgIdToEdit&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoingChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editedMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;editedMsg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;msgIdToEdit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msgIdToEdit&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the previous method, we use this one to publish a message to Ably indicating an edit. We include the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Username of the client editing it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New message content after edit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unique ID of the message that was edited&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have a good understanding of the methods and functionalities in the Nuxt app. Let's move on to the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 2 - PostgresDB setup for our chat app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; is an open-source object-relational database. This comes from their website:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PostgreSQL comes with many features aimed to help developers build applications, administrators to protect data integrity and build fault-tolerant environments, and help you manage your data no matter how big or small the dataset. In addition to being free and open-source, PostgreSQL is highly extensible. For example, you can define your own data types, build out custom functions, even write code from different programming languages without recompiling your database!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--foMYwHY_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdqda8uhm80tozd81nhd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--foMYwHY_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdqda8uhm80tozd81nhd.png" alt="Section 2 of the app architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've used &lt;code&gt;pg_admin&lt;/code&gt; to visualize the data and tables in the database and added a single table to store the data related to the chat app. I used the default &lt;code&gt;postgres&lt;/code&gt; database and created a new table with the columns needed in the chat data table via the GUI, along with the constraints for each. Here's the SQL version of the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_data&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"default"&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;msg_data&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;incremental_record_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chat_data_incremental_record_id_seq'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;created_at_timestamp&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_edited&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;chat_data_pkey&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The msg_id is a unique identifier for each chat message and is therefore a primary key. The incremental record ID is useful to retrieve x number of messages starting from a particular point. The &lt;code&gt;is_edited&lt;/code&gt; column indicates if the message has been edited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 3 - Lambda function setup on the Ably integrations dashboard
&lt;/h2&gt;

&lt;p&gt;We use a Lambda function to  insert and update rows in the &lt;code&gt;chat_data&lt;/code&gt; table dynamically. It is triggered when messages are published on the &lt;code&gt;outgoing_chat&lt;/code&gt; channel and that is set up in the Ably dashboard.&lt;/p&gt;

&lt;p&gt;External services to push or receive data in realtime, can be set up in the 'Integrations' dashboard of your Ably account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P7znQNmk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/qCu4_c8WSoTP-6OJ1GKuiQeJNikum6aMalPt59n7UAsBXksfCaAoZr_Pn0QinmGslKe9DC8xBLElfT_9U_VtW3yTyHQHi3HG-ks8B1_Oj-FgIRrLJXHi4h5k2Ynf8VtoMKgtumLC%3Ds0" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P7znQNmk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/qCu4_c8WSoTP-6OJ1GKuiQeJNikum6aMalPt59n7UAsBXksfCaAoZr_Pn0QinmGslKe9DC8xBLElfT_9U_VtW3yTyHQHi3HG-ks8B1_Oj-FgIRrLJXHi4h5k2Ynf8VtoMKgtumLC%3Ds0" alt="New Reactor rule setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on "New Reactor Rule", for the options to send or receive data from external systems. The chat app needs to push an event, i.e. trigger an AWS Lambda function, every time a new message is published on a channel. Select Reactor Event &amp;gt; AWS Lambda to set up the Lambda function along with the AWS credentials and the source of trigger for this endpoint. All of these &lt;a href="https://ably.com/documentation/general/events/aws-lambda#fields"&gt;fields are described further in the documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sJqS_7Gr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/SOh6sc1W_859L96M5VhPrTh73oP9hrh0lwdkHMsMC4-_vTxfxA0-iSJoZ58H7qwb1gaVXXHct39ClSteGLMQsQcgcZIAFe-ZeOXXnjsN54DvgI406dqGcDSO1jvKWX5ziWFtN6b5%3Ds0" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sJqS_7Gr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/SOh6sc1W_859L96M5VhPrTh73oP9hrh0lwdkHMsMC4-_vTxfxA0-iSJoZ58H7qwb1gaVXXHct39ClSteGLMQsQcgcZIAFe-ZeOXXnjsN54DvgI406dqGcDSO1jvKWX5ziWFtN6b5%3Ds0" alt="Integration rule settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the source as "Message" to trigger the Lambda when messages are published on a channel. In the channel filter field, we can specify the channel we'd like to use, which is &lt;code&gt;outgoing-chat&lt;/code&gt; in this instance. Note from the previous section about the front-end NuxtJS app, we use this channel to publish both new chat messages and edited messages. As you'll see later, the same Lambda function can handle both kinds of messages.&lt;/p&gt;

&lt;p&gt;Ably assigns a unique &lt;code&gt;msg_id&lt;/code&gt; to each message, which is available in its metadata, so we retain the default enveloped setting to allow for this data to be added in the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the Lambda function for database transactions
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is a serverless compute service that usually contains a single function's code to be triggered and executed as needed. To be able to use AWS Lambda functions, you need to have an account with AWS. You can then create a new function; ours uses the NodeJS environment.&lt;/p&gt;

&lt;p&gt;From AWS documentation for Lambdas with NodeJS:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Lambda function handler is the method in your function code that processes events. When your function is invoked, Lambda runs the handler method. When the handler exits or returns a response, it becomes available to handle another event.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/blob/main/lambda-function/index.js"&gt;Lambda function for the chat app&lt;/a&gt; performs either an INSERT operation or an UPDATE operation on the Postgres database, depending on the event data that invoked the function.&lt;/p&gt;

&lt;p&gt;It uses the &lt;code&gt;pg&lt;/code&gt; module to perform various operations on the Postgres table. It connects to the table by passing various parameters at the time of instantiating a new client, and calls the &lt;code&gt;connect()&lt;/code&gt; method to establish a connection to that table within the specified database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the incoming object (the event data that invoked the lambda) is formatted as required by the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;incomingObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msgData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the type of the message --- &lt;code&gt;chatMsg&lt;/code&gt; or &lt;code&gt;editedMsg&lt;/code&gt; --- the Lambda performs either an &lt;code&gt;INSERT&lt;/code&gt; operation in the database or an &lt;code&gt;UPDATE&lt;/code&gt; operation.&lt;/p&gt;

&lt;p&gt;a. Insert a new row for a new chat message published in the chat app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chatMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO chat_data(username, msg_id, msg_data, client_id, created_at_timestamp) VALUES($1, $2, $3, $4, $5)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queryValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`insert function done, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;queryValues&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; payload inserted`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;b. Update an existing row for an edit message published in the chat app. The specific row to be edited is identified using the unique identifier &lt;code&gt;msg_id&lt;/code&gt; which is a part of the &lt;code&gt;msgPayload&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editedMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE chat_data SET msg_data = $1, is_edited = $2 WHERE msg_id = $3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msgIdToEdit&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queryValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`update function done, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;queryValues&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; payload updated`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://node-postgres.com/features/queries#parameterized-query"&gt;Parameterized queries&lt;/a&gt; allow us to pass any parameters to the SQL queries without needing to worry about parsing special characters or in our case even emojis.&lt;/p&gt;

&lt;p&gt;With this setup, you can test out the working of the Lambda function and database updates, using the Nuxt chat app. Whenever you send a new message, it gets published on the 'outgoing-chat' channel, which in turn triggers the Lambda function, which in turn INSERTS or UPDATES the database table with the relevant data.&lt;/p&gt;

&lt;p&gt;However, you can see that this change actually doesn't come back to the frontend chat app just yet. We are making changes to the database, but we've not yet seen how the Ably-Postgres connector listens to these changes and publishes them to Ably. Let's explore this next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 4 - Set up the Ably Postgres connector for the chat app
&lt;/h2&gt;

&lt;p&gt;The Ably Postgres connector can be used via NPM or directly from the GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EU6MyBGa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryxmrf3t55v0oigutykp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EU6MyBGa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryxmrf3t55v0oigutykp.png" alt="Section 3 of the app architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's explore the second option. Clone the GitHub repository using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;https://github.com/ably-labs/ably-postgres-connector.git&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a &lt;a href="https://github.com/ably-labs/ably-postgres-connector/tree/main/lib#setup-config"&gt;few ways to add the configuration details of our Postgres database&lt;/a&gt;. The config file option applied to the chat app is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dbConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"connector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tablename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"ablychannelname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incoming-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INSERT"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tablename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"ablychannelname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incoming-chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UPDATE"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ably"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"API_KEY"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table name is set to &lt;code&gt;chat_data&lt;/code&gt; for &lt;code&gt;INSERT&lt;/code&gt; and &lt;code&gt;UPDATE&lt;/code&gt; operations. The &lt;code&gt;ablychannelname&lt;/code&gt; object indicates which channel the connector should publish into, following any INSERT or UPDATE operations done on the database/ table that the connector is watching.&lt;/p&gt;

&lt;p&gt;Run the connector on your local machine using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;examples
npm i
node with-json-config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you publish any messages or edit already published messages in the Nuxt chat app, you should see these coming back in the UI as the Nuxt app is subscribed to the &lt;code&gt;incoming-chat&lt;/code&gt; channel where the Postgres connector publishes the messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap of the app architecture 
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xr8iT431--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pal8za4v226hby9777sv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xr8iT431--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pal8za4v226hby9777sv.jpg" alt="Full app architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have now followed all the steps to enable our chat app to publish messages and receive messages on two different channels with database-driven updates enabled via the Ably Postgres connector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Let's take a look at how various components of the chat app are deployed to work together in a serverless and scalable way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuxt static app deployment on Netlify
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; provides a serverless, git-based workflow to deploy web applications. The chat app is a static site, meaning the HTML, CSS, and JS are pre-built and served to the users directly from CDN. In an &lt;a href="https://ably.com/blog/myth-busting-jamstack-cant-handle-dynamic-content"&gt;article I wrote earlier&lt;/a&gt;, I explained the JAMstack architecture in detail and the many benefits it provides:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jamstack proposes serving statically generated websites directly from the CDN, doing away with the need for an origin server to serve the site. This can be a bit confusing. To clarify, this doesn't mean that we can't have a server at all (we could have one for the app logic), but this server won't be responsible for serving our HTML page back to the user. In fact, the best option here would be to make use of the myriad serverless platform options available out there, to avoid having to manage any infrastructure in the backend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://nuxtjs.org/docs/2.x/deployment/netlify-deployment#configure"&gt;With Nuxt, you have built-in tooling to generate a static site that can be deployed directly to Netlify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What other options do I have?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Netlify is my personal favourite and the fact that it comes with serverless functions out of the box is a plus (as you'll see in the following section). There are &lt;a href="https://www.pluralsight.com/blog/software-development/where-to-host-your-jamstack-site"&gt;other options&lt;/a&gt; to deploy a front-end site built in Nuxt (or any other framework for that matter).&lt;/p&gt;

&lt;h3&gt;
  
  
  Ably Token-based authentication using Netlify functions
&lt;/h3&gt;

&lt;p&gt;When you instantiate Ably's libraries, you need to authenticate the client using &lt;a href="https://ably.com/documentation/core-features/authentication#basic-authentication"&gt;Basic authentication&lt;/a&gt; or &lt;a href="https://ably.com/documentation/core-features/authentication#token-authentication"&gt;Token authentication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While Basic authentication is mainly used for demos and quick prototypes, on a production level it is important to use Token authentication to ensure security. Implementing Token auth requires developers to set up an authentication endpoint that can check the legitimacy of the client and issue an &lt;a href="https://ably.com/documentation/realtime/authentication#token-request"&gt;Ably Token Request&lt;/a&gt;. The client can then use this Token Request to authenticate with Ably and use its services.&lt;/p&gt;

&lt;p&gt;Because we deployed the app to Netlify, it makes sense to use &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo/blob/main/chat-web-app/netlify/functions/ably-auth.js"&gt;Netlify serverless functions to authenticate our Ably clients&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Rest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABLY_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTokenRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clientId-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET, POST, PUT, DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we use the REST SDK as we don't need to set up a persistent connection with our clients. The communication is infrequent and REST requests are thus more efficient. Note that we are not actually verifying the clients against, say, a database, for the purposes of the demo. In a real-world scenario, the front-end client would pass some kind of identification, based on which the auth endpoint would decide whether or not it's a legitimate client.&lt;/p&gt;

&lt;p&gt;We assign a randomly generated unique &lt;code&gt;clientId&lt;/code&gt; to each client before returning it along with an Ably Token Request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What other options do I have?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can use either a dedicated authentication service such as Auth0, or other serverless function providers such as AWS Lambda Functions, Google Cloud Functions, and MS Azure Functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgresDB deployment on AWS RDS
&lt;/h3&gt;

&lt;p&gt;Up to this point, we have assumed the Postgres database to be running on a local machine. For the chat app, PostgresDB is deployed on Amazon's Relational Database Service (RDS). Given that we are manipulating data using an AWS Lambda function, it's easier to host the database on the same service to ensure maximum security. We can make the database part of the same VPC (Virtual Private Cloud) so all our component services have access to each other and can scale automatically as needed.&lt;/p&gt;

&lt;p&gt;AWS RDS is a scalable system where you can choose any one of the multiple database engine options available. One such option is PostgreSQL which is what we are working with. You can &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_CreateDBInstance.html"&gt;create a new Amazon RDS DB instance with the Postgres option&lt;/a&gt; for the database engine by following the documentation on AWS. You can create a new table for the &lt;code&gt;chat_data&lt;/code&gt; as we did locally before. If you already have some data stored in the local instance, you can also&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL.Procedural.Importing.html"&gt; import data into your Postgres table&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What other options do I have?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;There are many services built to host and work with PostgreSQL. They are &lt;a href="https://www.postgresql.org/support/professional_hosting/"&gt;listed in the Postgres documentation&lt;/a&gt;. Some of the popular serverless options other than AWS RDS are &lt;a href="https://www.heroku.com/postgres"&gt;Heroku&lt;/a&gt;, &lt;a href="https://landing.aiven.io/en/aiven-for-postgresql/"&gt;Aiven&lt;/a&gt;, and &lt;a href="https://www.digitalocean.com/products/managed-databases-postgresql/"&gt;Digital Ocean&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ably Postgres connector deployment on AWS Fargate (via ECS and ECR)
&lt;/h3&gt;

&lt;p&gt;Lastly, we need to deploy the Ably Postgres connector so it can continuously listen for any changes on the database and relay them to Ably to stream to millions of clients.&lt;/p&gt;

&lt;p&gt;For the chat app, I've used &lt;code&gt;docker-compose&lt;/code&gt; to create an image and stored this on &lt;a href="https://aws.amazon.com/ecr/"&gt;AWS ECR&lt;/a&gt; (Elastic Container Registry) which is used to store and share docker images. We can then host it using &lt;a href="https://aws.amazon.com/ecs/"&gt;AWS ECS&lt;/a&gt; (Elastic Container Service) that allows us to easily deploy, manage and scale the container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/fargate/"&gt;AWS Fargate&lt;/a&gt; is the serverless option to work with AWS ECS. Given that the PostgresDB is hosted on AWS (RDS), having the components that listen to this database (and need to have access to the database on AWS) also helps simplify the architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What other options do I have?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;There are other options supported by Docker such as Kubernetes and Azure ACI to create and deploy containers for use by your applications. You can find further information in their&lt;a href="https://docs.docker.com/language/nodejs/deploy/"&gt; documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further improvements
&lt;/h2&gt;

&lt;p&gt;With the architecture described in this article series, we completely depend on the database for all kinds of realtime messaging within the app. In a standard peer-to-peer Pub/Sub messaging scenario, there would be no dependence on the database for realtime messaging. Both of these have pros and cons and ideally, they'd need to be used in combination to achieve the best results. For example, in the editable chat app, we could publish regular messages directly to all the users on the same channel via Ably but only let the edited updates come via the database. This would massively reduce the latency and generally promote transient events without needing permanent storage.&lt;/p&gt;

&lt;p&gt;In terms of specific things we could improve in the current architecture and implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It is possible to &lt;a href="https://www.jeremydaly.com/reuse-database-connections-aws-lambda/"&gt;reuse database connections from the Lambda function&lt;/a&gt;, thereby reducing the latency and improving the performance of frequent changes made to the database via the Lambda function. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We could also &lt;a href="https://aws.amazon.com/blogs/compute/using-amazon-rds-proxy-with-aws-lambda/"&gt;do away with the Lambda function having to directly interact with the database by using the RDS proxy&lt;/a&gt; which handles connection pooling among other things.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As you may have noticed, currently the presence updates are streamed p2p, which may make it difficult to scale. To get around this, you can consider aggregating the presence state from various participants into a single message, then fanning this to everyone. Check out the &lt;a href="https://github.com/ably-labs/presence-collection"&gt;Ably Presence Collection&lt;/a&gt; project for more details on how to enable this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We could also replace Postgres on RDS with AWS DynamoDB and use its in-built feature, &lt;a href="https://aws.amazon.com/blogs/database/dynamodb-streams-use-cases-and-design-patterns/"&gt;DynamoDB streams&lt;/a&gt;, to stream updates to our end users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The front-end stack is all plug and play! You can conveniently replace Nuxt with any other frontend web or native mobile framework.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I hope this article series was a useful description and implementation of the database-driven architectural patterns. If you'd like to check out the chat app, you can do so at &lt;a href="https://serverless-scalable-chat.netlify.app/"&gt;https://serverless-scalable-chat.netlify.app/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also dive into the &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo"&gt;GitHub repo&lt;/a&gt;, remix the code or raise a PR to improve it further :)&lt;/p&gt;

&lt;p&gt;Here's a full index of both the articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/ablydev/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-1-1ceh"&gt;Database-driven realtime architectures: building a serverless and editable chat app - Part 1&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/ablydev/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-2-2lg1"&gt;Database-driven realtime architectures: building a serverless and editable chat app - Part 2&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As always, please feel free to &lt;a href="https://twitter.com/Srushtika"&gt;reach out to me&lt;/a&gt; if you have any questions :)&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>postgres</category>
      <category>database</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Database-driven realtime architectures: building a serverless and editable chat app - Part 1</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Tue, 28 Sep 2021 15:12:13 +0000</pubDate>
      <link>https://dev.to/ably/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-1-1ceh</link>
      <guid>https://dev.to/ably/database-driven-realtime-architectures-building-a-serverless-and-editable-chat-app-part-1-1ceh</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Database-driven realtime architectures are becoming more and more common as evidenced by key backers and widespread use of software like Firebase and Supabase.&lt;/p&gt;

&lt;p&gt;The two key priorities for an app following database-driven realtime messaging are long-term storage and change data capture (CDC) updates from the database.&lt;/p&gt;

&lt;p&gt;In this two part article series, we'll take a detailed look at a fully serverless chat app where you can edit previously published messages. The chat app uses the Ably Postgres connector to achieve this, the details of which you'll see throughout the article. You'll find the architecture and the example app useful if you are looking for ways to build database-driven applications that work seamlessly at scale.&lt;/p&gt;

&lt;p&gt;We've previously written about &lt;a href="https://ably.com/blog/realtime-and-databases-coupling-vs-modularity" rel="noopener noreferrer"&gt;decoupling databases from realtime messaging&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The main problem is lack of modularity: each tool should do its own job.  Databases are bases of data. They should do one thing and one thing well, which is to be the one base source of truth and data. They should never be saddled with realtime concerns --- that is not their strength by design. They should do storage, and a different dedicated piece should do realtime comms. All the other problems stem from this.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While the idea of a realtime database sounds great and opens up a huge range of possible use-cases one could build with this architecture, a tight coupling of databases and realtime messaging might suffer from various issues described in the article linked above.&lt;/p&gt;

&lt;p&gt;Moreover, not all event triggers constitute consequential payloads and thus don't need to go into storage. Perhaps some events are just transient to make a client aware of an event occurring, not necessarily even descriptive details about that event. For example, in a chat app, I'd be interested in storing messages, timestamps, etc. but not necessarily typing indicator events.&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://ably.com/blog/ably-postgres-connector" rel="noopener noreferrer"&gt;recent article&lt;/a&gt;, I introduced the &lt;a href="https://github.com/ably-labs/ably-postgres-connector" rel="noopener noreferrer"&gt;Ably-Postgres connector&lt;/a&gt; built by one of our community experts. It uses Postgres DB's listen/notify feature to listen for changes on any DB tables and publish updates on specific Ably channels whenever a change occurs. This allows us to take advantage of database-driven architectures without worrying about the scalability of realtime messaging or the awkward relationship between the two.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Check out the editable chat app:&lt;/em&gt; &lt;a href="https://serverless-scalable-chat.netlify.app/" rel="noopener noreferrer"&gt;&lt;em&gt;https://serverless-scalable-chat.netlify.app/&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The editable chat app architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let me present to you a complex looking architecture which will make more sense by the time you've worked through this article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F5900152%2F135112937-06a6326d-45b9-4cc3-9a00-ce4d694cfa18.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F5900152%2F135112937-06a6326d-45b9-4cc3-9a00-ce4d694cfa18.jpg" alt="Editable chat app architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From an end-user perspective, they will be publishing messages on the frontend app and expect to receive messages on it as well. The same goes with editing any messages: all participants will need a way to edit their own messages and also receive updates about any messages edited by others.&lt;/p&gt;

&lt;p&gt;A common architectural set-up when using a &lt;a href="https://ably.com/pub-sub-messaging" rel="noopener noreferrer"&gt;pub/sub messaging&lt;/a&gt; service like Ably is to publish updates on a channel to which the client is also subscribed. Although this works perfectly well for regular chat messages or any other event triggers, it is more complex to edit previously published messages or to trigger updates about changes to previous messages because Ably messages are immutable by nature.&lt;/p&gt;

&lt;p&gt;It is, however, possible to implement this functionality by using a slightly non-traditional approach. Instead of subscribing to a single chat channel to which users are publishing their messages, we can separate out the incoming and outgoing chat channels. Doing this allows us to perform various operations on the data before it comes back in a subscription callback. A common use case of this architecture is message filtering like applying a &lt;a href="https://en.wikipedia.org/wiki/Wordfilter" rel="noopener noreferrer"&gt;profanity filter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the case of the current chat app, we'll make use of a database to store all the published messages directly in a table. We'll also have a listener that can&lt;/p&gt;

&lt;p&gt;i) observe the &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt; changes in the chat data table of our database, and&lt;/p&gt;

&lt;p&gt;ii) publish a message on an Ably channel with the name of the operation as well as with the change data capture (CDC).&lt;/p&gt;

&lt;p&gt;If we make our front-end clients subscribe to this channel into which the listener is publishing database updates, we'll not only receive new messages as a result of &lt;code&gt;insert&lt;/code&gt; operations in the database, but also updates on previous messages resulting from &lt;code&gt;update&lt;/code&gt; operations on the database. Each Ably message comes with a unique &lt;code&gt;msgId&lt;/code&gt; assigned by Ably, so we can make use of this to uniquely identify each message in the table. The database will be the single source of truth in the app and also useful if we'd like to load previous messages in the chat like in the &lt;a href="https://realtime-chat-storage.ably.dev/" rel="noopener noreferrer"&gt;Ably-Airtable starter kit&lt;/a&gt; example.&lt;/p&gt;

&lt;p&gt;Before proceeding, take another look at the architecture diagram above to put all the steps in perspective and tie it all together.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tech stack&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We have four main goals with the editable chat app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Scalability&lt;/li&gt;
&lt;li&gt;  Serverless architecture&lt;/li&gt;
&lt;li&gt;  Editability of messages&lt;/li&gt;
&lt;li&gt;  Storage of messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In view of the above, let me explain some of the reasoning behind various tech choices in this chat app, along with some alternative options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F5900152%2F135114866-cf0dfbcc-9ce9-4fbb-b927-19827b9c3648.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F5900152%2F135114866-cf0dfbcc-9ce9-4fbb-b927-19827b9c3648.jpeg" alt="Tech stack for the editable chat app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NuxtJS for frontend web development
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; aims to provide best-practice solutions to common web development problems like routing, state-management, code splitting, etc. It allows us to make use of various NPM utility libraries in a static site that can be deployed and used directly from a CDN, without needing a server, i.e. following the &lt;a href="https://jamstack.org/what-is-jamstack/" rel="noopener noreferrer"&gt;Jamstack architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the case of our chat app, it is useful in terms of separating out state management entirely from the visual components, so developers of all tech stacks can understand the communication and data exchange between the chat app and external services (mainly Ably in this case).&lt;/p&gt;

&lt;p&gt;You can replace Nuxt with any other front-end web framework, vanilla JS or even use a native mobile programming language, depending on the needs and wants of your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ably to enable the pub/sub pattern
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ably.com/" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; is a realtime messaging infrastructure as a service. It allows you to enable &lt;a href="https://ably.com/pub-sub-messaging" rel="noopener noreferrer"&gt;publish/subscribe-based messaging&lt;/a&gt; in your application with just a few lines of code. Ably provides highly-reliable low-latency messaging, and is able to work globally on any platform or device. It completely abstracts away the complex problem of scaling realtime communications across multiple regions around the planet, so developers can focus on their app logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Lambda functions to insert and update rows in the database
&lt;/h3&gt;

&lt;p&gt;We use PostgresDB to store messages from the chat app. In general, any database transactions which change table data shouldn't be done directly from the front-end to avoid potential security risks. Hence, we'll make use of &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; functions to make changes to the database on the users' behalf. Given that we are aiming to make this app fully serverless, Lambda functions fit right in with this theme.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgresDB hosted on AWS RDS to store the data
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;Postgres&lt;/a&gt; is an open-sourced SQL database. Its performance and reliability make it a good choice for complex production applications. There's another special reason to choose Postgres as you'll see in the next point.&lt;/p&gt;

&lt;p&gt;Postgres doesn't come with hosting, we'll need to make use of another service to host the database. Again, in light of keeping everything serverless, I've made use of &lt;a href="https://aws.amazon.com/rds/" rel="noopener noreferrer"&gt;AWS RDS&lt;/a&gt; for Postgres. Using AWS also gives the advantage of the accessibility of the data between other AWS services, like the Lambda function in the previous point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ably Postgres connector to watch changes on the database tables and publish messages on every change
&lt;/h3&gt;

&lt;p&gt;One of the key requirements of this chat app is being able to listen to changes on the database tables and publish these changes to Ably. The &lt;a href="https://github.com/ably-labs/ably-postgres-connector" rel="noopener noreferrer"&gt;Ably Postgres connector&lt;/a&gt; is a community-built project which makes this possible. We use Postgres because the built-in &lt;a href="https://tapoueh.org/blog/2018/07/postgresql-listen-notify/" rel="noopener noreferrer"&gt;listen/notify feature&lt;/a&gt; makes this connector work. We'll take a detailed look at it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Fargate with AWS ECS and AWS ECR to deploy the Ably Postgres connector's dockerized image
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/fargate/" rel="noopener noreferrer"&gt;AWS Fargate&lt;/a&gt; is a serverless compute engine that hosts containers. The Ably Postgres connector has a dockerized image that needs to be hosted somewhere. We'll use AWS Fargate to do this, because it makes it easy and secure to manage the backend deployment and hosting on a single service like AWS.&lt;/p&gt;

&lt;p&gt;AWS Fargate works with &lt;a href="https://aws.amazon.com/ecs/" rel="noopener noreferrer"&gt;AWS ECS&lt;/a&gt; which enables deployment and management of containerized applications. We use &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;AWS ECR&lt;/a&gt; to upload the docker image so it can be stored in the registry to be used by ECS as needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Netlify to host the static Jamstack site
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; provides a serverless platform to deploy web applications. It also allows setting up git-based workflows to automate building and deploying new versions of a static site as changes are made to the repository. The Nuxt app is deployed using Netlify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Netlify functions to enable a token auth endpoint to authenticate with Ably
&lt;/h3&gt;

&lt;p&gt;Netlify's serverless platform also provides serverless functions which can be invoked to perform a piece of functionality. The Ably service requires clients to be authenticated in one of the two ways: basic authentication or token authentication. Basic authentication exposes the API Key directly in the frontend script, and thus shouldn't be used in production. You should almost always choose Token authentication. To enable this, we need to set up an authentication endpoint that can verify the credentials of the frontend client and issue Ably Token Requests. The frontend client can then use this Ably Token Request to authenticate with Ably and use its service.&lt;/p&gt;

&lt;p&gt;Given that we use Netlify to host the chat app, it's only natural that we make use of &lt;a href="https://www.netlify.com/products/functions/" rel="noopener noreferrer"&gt;Netlify functions&lt;/a&gt; to host our authentication endpoint. Even though AWS Lambda is already a part of the tech stack, it would require us to set up an IAM for our users before they can access AWS Lambda. Netlify, meanwhile, makes it very easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ably Postgres connector -- a key dependency
&lt;/h2&gt;

&lt;p&gt;Before moving on to the details of the chat app, let's first understand the working of the Ably Postgres connector that makes this architecture possible.&lt;/p&gt;

&lt;p&gt;I recently wrote an &lt;a href="https://ably.com/blog/ably-postgres-connector" rel="noopener noreferrer"&gt;article explaining the Ably Postgres connector in detail&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;The connector accepts a configuration file where you input the connection details for your database as well as for the tables you want to listen to for data changes. It also accepts an &lt;a href="https://knowledge.ably.com/setting-up-and-managing-api-keys" rel="noopener noreferrer"&gt;Ably API key&lt;/a&gt; to be able to instantiate and publish messages to your Ably app following any changes to the specified tables.&lt;/p&gt;

&lt;p&gt;Using the config file, the connector creates in your database a special table called the "ablycontroltable". This table is used to maintain the Ably channel mapping for different changes to the tables in your database.&lt;/p&gt;

&lt;p&gt;Next, the connector creates a procedure to listen to changes on the specified tables using the &lt;code&gt;pg_notify&lt;/code&gt; function. This notify function then publishes the change data capture (CDC) payload on the relevant Ably channel, as specified in the config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2FoTXZk-FCQeiH_QLoU2wvXfR94YqzPraB7cq0PzmIflKiuRuexXbHV5fP46oxQ9hL7fBpw_LGh-9kp345F9hq9Ek_6qdVcl4_zKUg9z69Rg7nZMMLObW4__pYZkOTEgNzoOiTriJE%3Ds0" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2FoTXZk-FCQeiH_QLoU2wvXfR94YqzPraB7cq0PzmIflKiuRuexXbHV5fP46oxQ9hL7fBpw_LGh-9kp345F9hq9Ek_6qdVcl4_zKUg9z69Rg7nZMMLObW4__pYZkOTEgNzoOiTriJE%3Ds0" alt="Ably Postgres Connector"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;With this, I hope you have a better understanding of the high-level architecture of the serverless editable chat app.&lt;/p&gt;

&lt;p&gt;In the next part of this two-part series, we'll take a closer look at various components of the chat app and dive into some code to better understand how each step is implemented.&lt;/p&gt;

&lt;p&gt;Here are a few things we'll see in the next part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Navigating the Nuxt app (even if you are not a Nuxt developer)&lt;/li&gt;
&lt;li&gt;  VueX state management&lt;/li&gt;
&lt;li&gt;  Postgres DB setup&lt;/li&gt;
&lt;li&gt;  Lambda functions setup on the Ably Integrations dashboard&lt;/li&gt;
&lt;li&gt;  Ably Postgres connector setup&lt;/li&gt;
&lt;li&gt;  Deployment of all the components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned and watch this space for the next article. In the meantime, check out the &lt;a href="https://serverless-scalable-chat.netlify.app/" rel="noopener noreferrer"&gt;demo of the editable chat app&lt;/a&gt; or &lt;a href="https://github.com/ably-labs/scalable-serverless-editable-chat-demo" rel="noopener noreferrer"&gt;dive into the GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://twitter.com/Srushtika" rel="noopener noreferrer"&gt;reach out to me&lt;/a&gt; if you have any questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://ably.com/blog/realtime-and-databases-coupling-vs-modularity" rel="noopener noreferrer"&gt;Realtime and databases --- a discussion on coupling versus modularity&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://ably.com/blog/myth-busting-jamstack-cant-handle-dynamic-content" rel="noopener noreferrer"&gt;Myth-busting: Jamstack can't handle dynamic content&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://ably.com/blog/ably-aws-web-components" rel="noopener noreferrer"&gt;Build your own live chat web component with Ably and AWS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://ably.com/topic/scaling-firebase-realtime-database" rel="noopener noreferrer"&gt;Scaling the Firebase Realtime Database beyond 200k users&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>postgres</category>
      <category>database</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Myth-busting: Jamstack can't handle dynamic content</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Mon, 19 Jul 2021 16:20:43 +0000</pubDate>
      <link>https://dev.to/ably/myth-busting-jamstack-can-t-handle-dynamic-content-4j16</link>
      <guid>https://dev.to/ably/myth-busting-jamstack-can-t-handle-dynamic-content-4j16</guid>
      <description>&lt;p&gt;Jamstack has brought forward a great way to rethink the infrastructure of modern-day websites. It shows us just how much we can abstract away in the process of serving websites and, as a result, gain tremendous benefits to User and Developer Experience.&lt;/p&gt;

&lt;p&gt;However, much confusion exists around what kind of websites can actually fall under this classification. The whole premise of Jamstack apps is based on the fact that these sites can be served directly from a CDN (Content Delivery Network), without needing an origin server. You might ask: “So these are static sites then? That means only pre-rendered content with nothing dynamic?” Well, that’s untrue and is one of the biggest myths around Jamstack.&lt;/p&gt;

&lt;p&gt;In this article, we’ll understand everything about Jamstack sites with dynamic content and specifically look at a realtime synced streaming application we built (dubbed as a live watch party app) to show off the wonderful benefits of Jamstack and the APIs around us, enabling us to elevate its possibilities. This app allows you and your friends to watch a video on your respective machines, synchronously, while chatting alongside - much like the Netflix watch party.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Jamstack?
&lt;/h2&gt;

&lt;p&gt;Before we take a stab at explaining anything here, we highly recommend &lt;a href="https://www.youtube.com/watch?v=YljH-aqKUFk"&gt;watching this video&lt;/a&gt; where Phil Hawksworth of Netlify takes us through a beautiful explanation of the Jamstack and why it is great.&lt;/p&gt;

&lt;p&gt;We’ve copied over one of his slides directly from the talk:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04y1274ctioo3ilcfgta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04y1274ctioo3ilcfgta.png" alt="JAMstack architecture" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The JAM in Jamstack stands for JavaScript, APIs, and Markup - pretty much everything we’ve been using already in most of our web apps.&lt;/p&gt;

&lt;p&gt;So, what’s different?&lt;/p&gt;

&lt;p&gt;It’s the way these apps are architected and served to users across the globe.&lt;/p&gt;

&lt;p&gt;As you see in the slide from Phil’s talk - for a traditional website that is dynamically served by a web server, the journey involves a few steps at the least. Your web browser goes to the CDN to get any static assets, then to the load balancer placed in front of the web servers capable of serving that particular site. The load balancer resolves which of the available web servers is best equipped to serve the page. The selected web server then serves the page back to the user following the same path. In some cases, the web server might request some data from the database before serving back the page to the client.&lt;/p&gt;

&lt;p&gt;In contrast to having all these components between the user and the page they want to see, Jamstack proposes serving statically generated websites directly from the CDN, doing away with the need for an origin server to serve the site. This can be a little bit confusing. To clarify - this doesn’t mean that we can’t have a server at all, we could have one for the app logic, but this server won’t be responsible for serving our HTML page back to the user. In fact, the best option here would be to make use of the myriad of serverless platform options available out there, to avoid managing any infrastructure in the backend.&lt;/p&gt;

&lt;p&gt;Although the initial site that’s loaded from the CDN is static, containing pre-rendered assets and data, we can immediately enhance the experience and functionality by retrieving dynamic data via API calls to our own server or any third-party endpoints.&lt;/p&gt;

&lt;p&gt;This results in many benefits, most obvious of which are improved performance and better user and developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bit more on static sites with dynamic/ realtime data
&lt;/h2&gt;

&lt;p&gt;There is a huge assumption that static sites mean static data. The static assets served by Jamstack projects can contain JavaScript files; after all the “j” in Jamstack represents JavaScript. Just as JavaScript brought dynamic data to websites in the 90s, it can still do the same today. We can use the static JavaScript files in our Jamstack projects to interact with out webpages and provide dynamic experiences for our end users - hook that up with a pub/sub or real-time infrastructure service like Ably, and we have dynamic data on the Jamstack very easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s understand with an example
&lt;/h2&gt;

&lt;p&gt;For this project, we've been working closely with &lt;a href="https://twitter.com/malgamves"&gt;Daniel Phiri&lt;/a&gt; and the &lt;a href="https://strapi.io/"&gt;Strapi&lt;/a&gt; team. It all started a couple of weeks ago when we started to build a realtime Jamstack app in public for the dev community to follow along:&lt;/p&gt;


&lt;blockquote&gt;
&lt;p&gt;So excited about this project &lt;a href="https://twitter.com/malgamves?ref_src=twsrc%5Etfw"&gt;@malgamves&lt;/a&gt; and I are working on! A live watch party app to host dedicated sync stream parties with your friends. We'll update this thread as we make progress &lt;a href="https://twitter.com/hashtag/BuildingInPublic?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#BuildingInPublic&lt;/a&gt; &lt;a href="https://t.co/5LnnVNzcSR"&gt;pic.twitter.com/5LnnVNzcSR&lt;/a&gt;&lt;/p&gt;— Srushtika Neelakantam (&lt;a class="mentioned-user" href="https://dev.to/srushtika"&gt;@srushtika&lt;/a&gt;) &lt;a href="https://twitter.com/Srushtika/status/1383003461659160591?ref_src=twsrc%5Etfw"&gt;April 16, 2021&lt;/a&gt;
&lt;/blockquote&gt; 

&lt;p&gt;&lt;a href="https://jamstack-watch-party.ably.dev/"&gt;The watch party app&lt;/a&gt; allows a host to select a video from the library and watch it synchronously with their friends anywhere on the globe.&lt;/p&gt;

&lt;p&gt;To give you a good idea, the host would follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enter their username, create a private watch party room, and share an invite link with friends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4wdo8bdv1gh897tq74l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4wdo8bdv1gh897tq74l.png" alt="Watch party homepage" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select a video from the library to watch along with friends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6359nfmtb8jex1vgrleq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6359nfmtb8jex1vgrleq.png" alt="Watch party gallery" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch the synchronized video with friends, share live comments, and see who’s currently online.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhw0zrjzn9zalxm2fz190.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhw0zrjzn9zalxm2fz190.png" alt="Watch party sync video" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The host gets to control the video playback -  if they play, the video starts playing for everyone else, same for pause, seek, and so on. If the host leaves, that’s the end of the party.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tech stack for our watch party app
&lt;/h2&gt;

&lt;p&gt;To build out this application, we’ve leveraged four pieces of technology - &lt;a href="https://nuxtjs.org/"&gt;Nuxt.js&lt;/a&gt;, &lt;a href="https://strapi.io/"&gt;Strapi&lt;/a&gt;, &lt;a href="https://ably.com/"&gt;Ably&lt;/a&gt; and &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. Let’s get into what each does for us in this project.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Nuxt.js?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nuxtjs.org/"&gt;Nuxt.js&lt;/a&gt; is a web framework based on Vue.js - besides being one of the most convenient ways to build out Vue applications, it gives us the option to build server-side rendered or static websites. For our project, we’ve gone with the static option and hosted these files on Netlify.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Strapi?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://strapi.io/"&gt;Strapi&lt;/a&gt; is an open source Node.js-based headless CMS. Since it's headless, we can use Strapi to deliver content to almost any digital device via its API. We’ve used Strapi as a video content manager. We’ve managed the videos available in our Nuxt.js app with Strapi, as well as leveraged its extra customisation options to build out a service to handle token requests with Ably.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Ably?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ably.com/"&gt;Ably&lt;/a&gt; is a realtime messaging infrastructure as a service solution. It allows you to enable publish/subscribe-based messaging in your application with just a few lines of code. Ably provides low-latency messaging, high reliability, and is able to work globally on any platform or device. It completely abstracts away the complex problem of scaling realtime communication across multiple regions, from the developer space.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Netlify?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; is a cloud hosting company that offers hosting and serverless backend services for web applications and static sites. It is particularly known for bringing the Jamstack approach of building websites to the attention of developers around the world.&lt;/p&gt;
&lt;h2&gt;
  
  
  App architecture - bringing all these technologies together
&lt;/h2&gt;

&lt;p&gt;Jamstack sort of forces you to have a simplified architecture and infrastructure for your web app. For the watch party, the static site itself (that is just the initial page that allows hosts to create private watch party rooms) is hosted on Netlify’s CDN.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Both the admin version and non admin version of the site can be retrieved directly from the CDN (based on URL routing).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ably’s Pub/Sub platform requires you to authenticate before you can use the service. There are two options for this - either &lt;a href="https://ably.com/documentation/core-features/authentication#basic-authentication"&gt;embed the API key directly&lt;/a&gt; into the front-end web app (which would be a bad idea because anyone can steal it), or use &lt;a href="https://ably.com/documentation/core-features/authentication#token-authentication"&gt;Token authentication&lt;/a&gt; by requesting an auth server to help the front-end clients to authenticate securely. We’ll use Strapi as our auth server (in addition to its beautiful CMS capabilities which we’ll touch upon soon).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After we’ve received an &lt;a href="https://ably.com/documentation/core-features/authentication#token-request-process"&gt;Ably Token Request&lt;/a&gt; back from Strapi, we can send it to Ably to securely authenticate with the service and initialize the SDK. This sets up a persistent realtime connection with Ably, allowing any new updates to be pushed directly to our app and vice versa. We’ll use this to synchronize the video stream, as well as to share comments and live online status of participants.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the host has authenticated with Ably (and transparently with Strapi via dummy user credentials), they’ll be able to share an invite link with any participants they’d like to invite to their private watch party.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, the host will be able to request the video library from the Strapi CMS. This will show them a grid of various videos to choose from. After they’ve chosen a video, the unique reference code for that video will be immediately published to all the participant apps via Ably. These non-admin participants can then (behind the scenes) request the particular video resource directly from the Strapi CMS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On this final screen, everyone will be able to add live comments and it is up to the host to play the video, pause it, seek it to a certain timestamp etc - all of which would be synchronized with the rest of the viewers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftavsovowu36l4njdism6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftavsovowu36l4njdism6.png" alt="Live watch party app architecture" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code snippets and explanations
&lt;/h2&gt;

&lt;p&gt;Let’s understand the main components of the app.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a video collection and adding content to Strapi
&lt;/h3&gt;

&lt;p&gt;After &lt;a href="https://strapi.io/documentation/developer-docs/latest/getting-started/introduction.html"&gt;getting started&lt;/a&gt; with your Strapi app, a browser tab will open and take you to the Admin Panel. Create a user and log in. Once that's done, we can start building out the content schema to store our videos. Once you’re in, navigate to Content-Types Builder under Plugins in the left-hand menu.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the "+ Create new collection type" link.&lt;/li&gt;
&lt;li&gt;Name it videos and click Continue.&lt;/li&gt;
&lt;li&gt;Add a Text field (short text) and name it title.&lt;/li&gt;
&lt;li&gt;Click the "+ Add another field" button.&lt;/li&gt;
&lt;li&gt;Add another Text field (long text) and name it description.&lt;/li&gt;
&lt;li&gt;Click the "+ Add another field" button.&lt;/li&gt;
&lt;li&gt;Add a Media field and name it video.&lt;/li&gt;
&lt;li&gt;Click the "+ Add another field" button.&lt;/li&gt;
&lt;li&gt;Add another Media field and name it thumbnail.&lt;/li&gt;
&lt;li&gt;Click the Save button and wait for Strapi to restart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything should look like this once done:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppo4ztpf71gzs1mj7ciy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppo4ztpf71gzs1mj7ciy.png" alt="Strapi collection types" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These fields will store the video details for your pages. Now we can go on and add content to them by clicking Videos on the left menu. Adding content should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvk7mywe930tjwzt3d5bb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvk7mywe930tjwzt3d5bb.gif" alt="Strapi dashboard" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Hosting Strapi
&lt;/h3&gt;

&lt;p&gt;Strapi is a self-hosted headless CMS. With that in mind you have an array of deployment options, most of which are laid out nicely in Strapi’s &lt;a href="https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/deployment.html"&gt;Deployment Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For our project, we deployed our Strapi App to Heroku following the &lt;a href="https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.html"&gt;guide provided&lt;/a&gt;. We’d recommend this approach if you want a free hosting provider that lets you provision a Postgres database for your Strapi app with minimal effort.&lt;/p&gt;
&lt;h3&gt;
  
  
  Retrieving info from Strapi into our app
&lt;/h3&gt;

&lt;p&gt;Before we can make requests to our Strapi server, we need to make sure that we have the right permissions set up to get them. To do this, we go to your User Permissions &amp;gt; Roles &amp;gt; Public Role &amp;gt; click the find and find one checkbox under videos, as well as the auth checkbox under ably-auth as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngb6p7e4wkgl42bqujzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngb6p7e4wkgl42bqujzo.png" alt="Strapi permissions" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the project we’re using the Strapi GraphQL API, so we’ll have to install it with yarn strapi install graphql - you have the option to use the &lt;a href="https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#api-endpoints"&gt;REST API&lt;/a&gt; and the &lt;a href="https://strapi.nuxtjs.org/"&gt;Strapi Nuxt module&lt;/a&gt; too.&lt;/p&gt;

&lt;p&gt;Once the GraphQL plugin is installed, we can go to &lt;code&gt;http://localhost:1337/graphql&lt;/code&gt; to access our GraphQL Playground and play around with different GraphQL operations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Authenticating Ably via Strapi
&lt;/h3&gt;

&lt;p&gt;Ably’s realtime messaging service expects client devices to be authenticated before they can start using the service. This can be done in two ways - either by using the API key directly in the front-end app (&lt;a href="https://ably.com/documentation/core-features/authentication#basic-authentication"&gt;Basic Authentication strategy&lt;/a&gt;) or via tokens issued by an auth server (&lt;a href="https://ably.com/documentation/core-features/authentication#token-authentication"&gt;Token Authentication strategy&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;As you might have guessed, embedding the API key wouldn’t be a wise choice because it can be easily misused. To implement Token Auth strategy, we’ll need to have a backend service use a direct API Key securely on our behalf and generate a valid token request via Ably. The frontend client can then use this token request to authenticate with Ably without leaking the API Key details. You can &lt;a href="https://www.youtube.com/watch?v=FHkzs8z3J5E"&gt;learn more about it in a short YouTube video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For our app, since we are already using Strapi as a CMS for our data, we’ll also make use of it as a backend service generating our token request.&lt;/p&gt;

&lt;p&gt;We took advantage of Strapi &lt;a href="https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#requests-responses"&gt;custom controllers&lt;/a&gt; and built out the logic to generate a token request for Ably.  This is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ably/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ABLY_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABLY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ABLY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;echoMessages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ablyThing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTokenRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ablyThing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ablyThing&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;badRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Daas not good!!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pub/Sub messaging with Ably
&lt;/h3&gt;

&lt;p&gt;Ably’s core offering is a scalable realtime messaging that follows the &lt;a href="https://ably.com/topic/pub-sub"&gt;Publish/Subscribe pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ably.com/login"&gt;Go to the dashboard&lt;/a&gt;, where we can divide realtime data into different categories, based on what the data represents and which participants are concerned with that data. These different categories of data can be published on different ‘&lt;a href="https://ably.com/documentation/core-features/channels"&gt;channels&lt;/a&gt;’ within the app. Here’s an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ably&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;jamstack&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;news&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Publish a message to the jamstack-news channel&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe to messages on jamstack-news channel&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the watch party app, we’ve used the following channels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;mainParty&lt;/code&gt;: used mainly to share presence data (this is explained below in this article).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video&lt;/code&gt;: used to share updates related to the video player, including play, pause and seek events, along with the current timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;comments&lt;/code&gt;: used to share live comments between participants of the specific watch party.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Unique channels
&lt;/h4&gt;

&lt;p&gt;Given that we use the same app to allow different groups of people to spin up their own breakout room, we also need to think about a way to separate out the realtime messages for each of those rooms. To do this, we assign a unique random code to each watch party room and use that to uniquely identify channels in the same Ably app. Given that different channels can have different participants and the data from one channel doesn’t go into another, unless explicitly published, this should be a good way for us to separate out concerns.&lt;/p&gt;

&lt;p&gt;Another option is to use &lt;a href="https://ably.com/documentation/general/channel-rules-namespaces#title"&gt;channel namespaces&lt;/a&gt;. These are useful when we want to apply certain features or restrictions to a set of channels as a whole. As we won’t be needing that for this app, we’ve just gone with the channel names to be &lt;code&gt;watch-party-&amp;lt;random-room-code&amp;gt;&lt;/code&gt;, &lt;code&gt;video-&amp;lt;random-room-code&amp;gt;&lt;/code&gt; and &lt;code&gt;comments-&amp;lt;random-room-code&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Utilizing Nuxt’s central store for efficient fan out of messages to required components
&lt;/h4&gt;

&lt;p&gt;We’ve made use of the VueX store, which comes built into Nuxt. You can find this in &lt;code&gt;store/index.js&lt;/code&gt;. This file works as a central store for most of the data in our static site. A typical VueX store contains four objects (possibly more depending on your specific app) - state, getters, mutations and actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State&lt;/strong&gt;: This is a single object containing the application level state which represents the single source of truth and allows different components to be in sync with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getters&lt;/strong&gt;: Getters are methods that allow us to compute derived states to be used anywhere in the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutations&lt;/strong&gt;: Mutations are methods that change the value of a certain state object. Mutations should always be synchronous - this is to ensure that we have a good view of the state changes. If you need to update the state based on an asynchronous operation, you’d use actions described next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actions&lt;/strong&gt;: You’d use actions to perform asynchronous operations and call a mutation when ready to change the state as a result of that async operation.&lt;/p&gt;

&lt;p&gt;This central store is especially useful for the watch party app, because we have various channels, the async data from which is being used in different components. And because VueJS is reactive, our components can watch for changes on any of the variables and react to them immediately with UI updates.&lt;/p&gt;

&lt;p&gt;The key things to notice in the store for our project are listed below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;currentVideoStatus&lt;/code&gt; state object:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;currentVideoStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;isVideoChosen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;didStartPlayingVideo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;chosenVideoRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;isPaused&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a single source of information about the video being played. For the host, this is always in sync with their video player. We publish this object whenever a new non host participant joins. This is also the object published when an existing participant clicks on the ‘force sync with admin’ button.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;instantiateAbly()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this method, we instantiate Ably using Token authentication. As described previously, Token authentication is facilitated by a Strapi endpoint. So, in the init method, we pass in the url of that endpoint as a value to the authUrl object. We receive a client id when the connection is successful, which we then save in a local state object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ablyInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth-ably&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;attachToAblyChannels()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this method, we attach to the three channels. Note that we add the unique room code to these channel names to make sure they are uniquely identified for this watch party room, across the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;attachToAblyChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;//mainPartyChannel&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mainParty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mainParty&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
           &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
           &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watchPartyRoomCode&lt;/span&gt;
       &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// similarly for the video and comments channels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;subscribeToChannels()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this method, we subscribe to the channels we previously attached to. When a new update is published on that channel, the respective callback method will be triggered. We simply update the state variables to contain the latest message that has arrived.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentsChMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;publishCurrentVideoStatus()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method enables the admin to publish the currentVideoStatus object we described previously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;updateEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentVideoStatus&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;requestInitialVideoStatus()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method is used by non admin participants to request the latest video status. This is invoked once at the beginning when they’ve just joined, then again whenever they click on the &lt;code&gt;force sync&lt;/code&gt; button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;requestInitialVideoStatus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-status-request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;publishMyCommentToAbly()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method publishes the user's comments. This will be displayed in the list of comments next to the video player.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;publishMyCommentToAbly&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;commentMsg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;commentMsg&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The utility methods are self explanatory but the rest of the methods are described in the next section.&lt;/p&gt;

&lt;h4&gt;
  
  
  Presence
&lt;/h4&gt;

&lt;p&gt;Presence is an Ably feature that you can use to subscribe to realtime changes to a device or client’s online status (aka their connection status). Presence allows us to see who is currently online in the watch party room. This information is displayed in a tab next to the live comments. A live counter of the number of people online is also displayed above the video player for a quick look.&lt;/p&gt;

&lt;p&gt;Here’s some explanation of the presence related methods in the store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;getExistingAblyPresenceSet()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apart from a live subscription to ongoing presence updates, we also need a list of people who were already there when a user joins. In this method, we perform an API request to get the existing presence set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mainParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{....});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;subscribeToAblyPresence()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this method we set up a subscription to presence on the main party channel and invoke various methods to handle new people joining or existing people leaving.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mainParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{....});&lt;/span&gt;
       &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mainParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;leave&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{....));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;handleNewMemberEntered()&lt;/code&gt; and &lt;code&gt;handleExistingMemberLeft()&lt;/code&gt; methods:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these methods we update our local array with the latest presence set information and also update our local counters reflecting the aggregate number of people present in the watch party at any given time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;enterClientInAblyPresenceSet()&lt;/code&gt; method:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this method, we make the current client enter the presence set on the main party channel. This will publish an update to everyone else who is subscribed to the presence set and also include this user in the global presence set.&lt;/p&gt;

&lt;h4&gt;
  
  
  History and Rewind
&lt;/h4&gt;

&lt;p&gt;Given that Ably is a &lt;a href="https://ably.com/pub-sub-messaging"&gt;pub/sub messaging service at its core&lt;/a&gt;, almost all of the messaging is transient. While Ably doesn’t store messages in the long term, it does provide storage options up to a certain extent. For example, you saw in the previous sections we were able to retrieve the presence set via an API call. Similar to that, even for regular messages on regular channels, Ably offers two ways to retrieve previously published messages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ably.com/documentation/core-features/history"&gt;History&lt;/a&gt; - an explicit API call to request previously published messages.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ably.com/documentation/realtime/channels/channel-parameters/rewind"&gt;Rewind&lt;/a&gt; - allows you to get some previously published messages at the time of attaching and subscribing to a channel for realtime messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can use rewind on the comments channel so that all the participants are able to see the comments published even before they join the watch party. With rewind, we can either specify a time period or number to indicate how many previously published messages we’d like to retrieve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting static sites with Netlify
&lt;/h3&gt;

&lt;p&gt;As mentioned above when we introduced the service, we’ll deploy our watch-party app to Netlify!&lt;/p&gt;

&lt;p&gt;To start out, create a &lt;a href="https://netlify.com/"&gt;Netlify account&lt;/a&gt; and make sure your project source code is hosted on GitHub. Click “Create new site from Git” and connect your GitHub to Netlify. Select your repo and fill in the details. Under Basic Build Settings, your build command should be yarn generate, and your publish directory should be dist. Select Advanced Settings and define your environment variables, add API_URL to key, and replace  with the URL of your deployed Strapi app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4b3w4lqp5u11dy20624.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4b3w4lqp5u11dy20624.png" alt="Netlify dashboard" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s worth noting that should you have both your Strapi app and watch-party apps in a monorepo configuration (both apps in the same Git repository) like our repository, then you need to add a base directory as shown below. These settings are available in Site Settings under Build &amp;amp; Deploy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpzkju6022z8owxcfcn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpzkju6022z8owxcfcn3.png" alt="Netlify build settings" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Should you have any trouble, you can reference the Nuxt &lt;a href="https://nuxtjs.org/docs/2.x/deployment/netlify-deployment/"&gt;documentation on deploying&lt;/a&gt; to Netlify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add-on options via Webhooks to further enhance our app
&lt;/h2&gt;

&lt;p&gt;In terms of your product-specific custom architecture, you may want to add other components (such as a database), maybe trigger a cloud function to perform some computation, or even stream messages to a third-party service. Ably provides easy ways to &lt;a href="https://ably.com/integrations"&gt;integrate with external APIs and services&lt;/a&gt; via webhooks, serverless functions, message queues, or event streaming. You can also use incoming webhooks to trigger a message on an Ably channel from an external service. (Think of a scenario where you allow participants to answer your quiz via SMS messages!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We’ve built a realtime Jamstack app and busted the myth. Jamstack CAN handle dynamic content.  Jamstack is a great concept and works well if applied correctly.&lt;/p&gt;

&lt;p&gt;I hope this article has given you a good view into realtime Jamstack apps, and got you quickly up and running with Strapi and Ably. It has been great to collaborate with Daniel on this project! We've also done a webinar together: &lt;a href="https://app.livestorm.co/strapi/how-to-build-a-realtime-watch-party-jamstack"&gt;Realtime data on the Jamstack with Ably and Strapi&lt;/a&gt;, where we've talked about the watch-party app and done some live Q&amp;amp;A.&lt;/p&gt;

&lt;p&gt;You can check out the watch party yourself at: &lt;a href="https://jamstack-watch-party.ably.dev/"&gt;https://jamstack-watch-party.ably.dev/&lt;/a&gt;. Have feedback or want to exchange ideas? You can always find me on Twitter: &lt;a href="https://twitter.com/Srushtika"&gt;@Srushtika&lt;/a&gt;. Happy to any questions too, my DMs are open!&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>vue</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Airtable as a database to store realtime messages</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Wed, 16 Dec 2020 13:18:43 +0000</pubDate>
      <link>https://dev.to/srushtika/using-airtable-as-a-database-to-store-realtime-messages-2eia</link>
      <guid>https://dev.to/srushtika/using-airtable-as-a-database-to-store-realtime-messages-2eia</guid>
      <description>&lt;p&gt;In this article, we'll see how to use Airtable to store realtime messages using a group chat app as an example. We'll use Ably's realtime infrastructure to power the chat app and make use of WebHooks to publish messages to Airtable directly in the correct order from Ably.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://github.com/ably-labs/ably-airtable-starter-kit" rel="noopener noreferrer"&gt;full source code of the group chat app written in VueJS on GitHub&lt;/a&gt; and the live demo of the application at &lt;a href="https://realtime-chat-storage.ably.dev/" rel="noopener noreferrer"&gt;https://realtime-chat-storage.ably.dev/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Airtable?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://airtable.com/" rel="noopener noreferrer"&gt;Airtable&lt;/a&gt; describes itself as 'Part spreadsheet, part database, and entirely flexible' and that's exactly what it is to the word. It caters to the engineering and commercial departments in an organization alike with its robust REST API and very nice visual UI with custom fields to manage and represent the data. It combines a bunch of different tools like task managers, databases, CRMs, spreadsheets, etc, into a single product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2Fgrid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2Fgrid.png" alt="Example of an Airtable table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Airtable REST API
&lt;/h3&gt;

&lt;p&gt;Airtable comes with a simple &lt;a href="https://airtable.com/api" rel="noopener noreferrer"&gt;REST API&lt;/a&gt; to perform the basic &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete" rel="noopener noreferrer"&gt;CRUD operations&lt;/a&gt; on the data stored. You'll need to have a base i.e. a table/ sheet set up before you can check out the documentation. This is for a good reason - their whole documentation is dynamically displayed with real keys, ids, column names etc, along with sample responses, based off your data, making it super easy for you to just copy out the code and use as is. They provide this documentation in cURL and JavaScript. The JS code snippets require using the &lt;a href="https://github.com/Airtable/airtable.js" rel="noopener noreferrer"&gt;Airtable JavaScript Client SDK&lt;/a&gt;. Here's a look at the documentation for the chat app base.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2FScreenshot-2020-11-24-at-18.59.41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2FScreenshot-2020-11-24-at-18.59.41.png" alt="Airtable API showing the Ably chat app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use Airtable as a database
&lt;/h3&gt;

&lt;p&gt;In this example, we'll look at two operations - to store and retrieve data from Airtable. We'll make use of WebHooks to send 'Create records' REST requests to the Airtable API each time a new chat message is published. We'll then make use of 'List records' to retrieve previously stored messages upon user request. &lt;a href="https://airtable.com/shrBExmY1lK3yyupo/tble6R4YjCEuP8A7x" rel="noopener noreferrer"&gt;Check out a subset of the database&lt;/a&gt; so you have an idea of the schema, or to simply put it, the column names in our database table/spreadsheet.&lt;/p&gt;

&lt;p&gt;Each new message will have a unique (randomly created) &lt;code&gt;msgId&lt;/code&gt;. This will be our primary key. The data is pre-ordered in ascending order by the &lt;code&gt;ID&lt;/code&gt; column, which is an incremental number assigned to every new record automatically by Airtable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Realtime updates with Ably and WebHooks
&lt;/h2&gt;

&lt;p&gt;If you already use &lt;a href="https://ably.com/" rel="noopener noreferrer"&gt;Ably&lt;/a&gt;, you can skip this section, if not, you can get started by &lt;a href="https://ably.com/signup" rel="noopener noreferrer"&gt;creating an account&lt;/a&gt;. Ably provides a reliable realtime messaging infrastructure with high scalability. It primarily operates over WebSockets and provides Pub/Sub messaging infrastructure out of the box. It is protocol and platform agnostic in the sense that you can use it with &lt;a href="https://ably.com/topic/websockets" rel="noopener noreferrer"&gt;WebSockets&lt;/a&gt;, &lt;a href="https://ably.com/topic/mqtt" rel="noopener noreferrer"&gt;MQTT&lt;/a&gt; or &lt;a href="https://ably.com/topic/server-sent-events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt;, and with any language and platform that you are working with. You don't have to spend time understanding the hard distributed systems problems it solves but simply start &lt;a href="https://ably.com/pub-sub-messaging" rel="noopener noreferrer"&gt;publishing and subscribing to realtime data&lt;/a&gt; with a mere couple of lines of code.&lt;/p&gt;

&lt;p&gt;We'll make use of &lt;a href="https://ably.com/documentation/realtime" rel="noopener noreferrer"&gt;Ably's JavaScript Realtime SDK&lt;/a&gt; to power the chat app and the &lt;a href="https://ably.com/integrations" rel="noopener noreferrer"&gt;WebHook Integration feature&lt;/a&gt; to integrate Airtable directly with an Ably app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2FScreenshot-2020-11-24-at-19.32.23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2FScreenshot-2020-11-24-at-19.32.23.png" alt="Ably has many ways to extend the platform, including WebHooks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In terms of flexibility, Airtable and Ably are a perfect match as you can use both these platforms in exactly the way that suits your custom use-case.&lt;/p&gt;

&lt;p&gt;Ably's Pub/Sub messaging is implemented using the concept of '&lt;a href="https://ably.com/documentation/core-features/channels" rel="noopener noreferrer"&gt;channels&lt;/a&gt;'. Each Ably app can have any number of channels where each channel carries a group of information. For example, in a logistics app, you would have one channel for location updates of the fleet and another for job updates to inform any changes to delivery conditions etc. Depending on the data, you can set permissions as to who can publish or subscribe to the data on that channel by attaching to it. You can &lt;a href="https://ably.com/documentation/core-features/channels" rel="noopener noreferrer"&gt;learn more about channels in the official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are WebHooks?
&lt;/h3&gt;

&lt;p&gt;In simple terms, &lt;a href="https://ably.com/topic/webhooks" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt; are user-defined HTTP callbacks (or small code snippets linked to a web application) that get triggered when specific events take place on an external website or service. They're especially useful when you're building notification functions and event-driven responses in applications. You can &lt;a href="https://ably.com/topic/webhooks" rel="noopener noreferrer"&gt;learn more about WebHooks in the conceptual deep-dive article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;WebHooks are a great fit for our use case - sending a message to an Airtable base as a result of an event i.e. a new chat message published on a specific channel. If you go to the Reactor tab on the Ably app dashboard after logging in/ signing up, you should be able to create a 'New Reactor Rule' and select the &lt;em&gt;Reactor Event &amp;gt; WebHook&lt;/em&gt; option. In a reactor rule, you essentially configure an HTTP endpoint along with the relevant headers, format, etc. You then select the source of the event trigger. There are a few options here - 'Presence', 'Message' and 'Channel Lifecycle. All we need is a regular 'Message' in this case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2Fairtable-reactor-rule.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2Fairtable-reactor-rule.png" alt="Setting up the Webhook integration with Airtable using Ably's Reactor Integration Rules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll also see options to batch the requests or envelop them with Ably metadata. You can choose the batch option if you expect the request to be triggered at high frequency. That'll prevent you from hitting the rate limit on Airtable, which at the time of this writing is 30 requests/ sec. We won't be enveloping the message with Ably metadata as Airtable expects the requests to be in a certain format exactly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing it all together in a group chat app built with VueJS
&lt;/h2&gt;

&lt;p&gt;The group chat demo is written in VueJS. Here's an illustration to better understand how all the components fit together:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F12%2FScreenshot-2020-12-04-at-13.12.33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F12%2FScreenshot-2020-12-04-at-13.12.33.png" alt="Communication architecture showing how Ably, Airtable and the Chat app fit together."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In terms of the folder structure you see in the GitHub project, the following are the main files of interest to us in this article.&lt;/p&gt;

&lt;p&gt;ably-airtable-storage\&lt;br&gt;
| __ src\&lt;br&gt;
| __ | __ App.vue\&lt;br&gt;
| ______ | __ components\&lt;br&gt;
| __________ | __ infobox\&lt;br&gt;
| __________ | __ chatbox\&lt;br&gt;
| ______________ | __ ChatCard.vue\&lt;br&gt;
| ______________ | __ UsernameInput.vue\&lt;br&gt;
| ______________ | __ ChatMessage.vue\&lt;br&gt;
| ______________ | __ ChatInput.vue\&lt;br&gt;
server.js&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ably-airtable-storage&lt;/code&gt; folder holds the VueJS app whereas the &lt;code&gt;server.js&lt;/code&gt; file in the root serves the VueJS app and issues auth tokens to the front-end app to authenticate with Ably. (More on this later)&lt;/p&gt;

&lt;p&gt;As you saw in the &lt;a href="https://realtime-chat-storage.ably.dev/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;, we also have an information box on the side which shows the play-by-play of behind the scenes as you use the app. You can make use of that to understand what exactly is happening in each step and use the code snippets to try it out yourself. The code for this is in the &lt;code&gt;infobox&lt;/code&gt; folder under the &lt;code&gt;components&lt;/code&gt; folder. We won't be discussing much about the information box in this article.&lt;/p&gt;

&lt;p&gt;Let's take a look at what's going on in the rest of the files.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;server.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a super simple &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express server&lt;/a&gt; which serves the &lt;code&gt;index.html&lt;/code&gt; page from the &lt;code&gt;dist&lt;/code&gt; folder of the Vue app. The dist folder is generated when you run the build command after you are done working on the Vue app. You can learn more about this in &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;VueJS docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll notice that we also have an &lt;code&gt;/auth&lt;/code&gt; endpoint. As mentioned before, this is to issue tokens so the Vue app can authenticate securely with Ably's realtime service. Ably offers two ways of authenticating - &lt;a href="https://ably.com/documentation/core-features/authentication#basic-authentication" rel="noopener noreferrer"&gt;Basic Auth&lt;/a&gt; and &lt;a href="https://ably.com/documentation/core-features/authentication#token-authentication" rel="noopener noreferrer"&gt;Token Auth&lt;/a&gt;. Basic auth uses the API Key directly whereas token auth expects auth tokens or JWT, making it a more secure way of authenticating the front-end applications. You can learn more about each of these types and trade-offs in &lt;a href="https://ably.com/documentation" rel="noopener noreferrer"&gt;Ably's documentation&lt;/a&gt; and &lt;a href="https://ably.com/documentation/best-practice-guide" rel="noopener noreferrer"&gt;best practice guide&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The VueJS chat app
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;App.vue&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the main parent component for the entire app. So, a good place to instantiate and manage the connection with Ably.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We instantiate Ably in the &lt;code&gt;created()&lt;/code&gt; lifecycle hook of this component and disconnect in the &lt;code&gt;destroyed()&lt;/code&gt; lifecycle hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;created&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myClientId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAblyConnected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatChannelInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatChannelId&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;destroyed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ablyRealtimeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;authUrl&lt;/code&gt; object sent to the &lt;code&gt;Ably.Realtime&lt;/code&gt; instance prompts Ably that we are looking to authenticate via token auth via the given URL to automatically renew tokens just before they expire.&lt;/p&gt;

&lt;p&gt;After the connection status becomes connected, we get an instance of the channel to subscribe to later. If you remember from the previous step, we'd need to use the &lt;code&gt;chat-airtable&lt;/code&gt; channel name for publishing and subscribing to the chat messages as that is the channel we are using to trigger messages sent to the Airtable database. If you notice, the full name we specify, however, is &lt;code&gt;[?rewind=2m]chat-airtable&lt;/code&gt;. The channel name is preceded by some meta-information enclosed in the square brackets. The channel param used there is &lt;a href="https://ably.com/documentation/realtime/channels/channel-parameters/rewind" rel="noopener noreferrer"&gt;Rewind&lt;/a&gt; with a value set to 2 minutes. This allows you to get any previously published messages in the last 2 minutes before successfully establishing a connection to Ably and attaching to the channel. You can &lt;a href="https://ably.com/documentation/realtime/channels/channel-parameters/overview" rel="noopener noreferrer"&gt;learn more about all the available channel params from Ably's docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;2. &lt;code&gt;ChatCard.vue&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the parent component for the Group chat app, so we subscribe to updates on the chat channel here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;created&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReadyToChat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatChannelInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleNewMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We subscribe to the chat channel and call a new method to handle the new message every time the callback is invoked. More on this shortly.&lt;/p&gt;

&lt;p&gt;This component has three child components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  UsernameInput.vue - accepts user's name before they join the chat&lt;/li&gt;
&lt;li&gt;  ChatInput.vue - accepts users' chat message if they'd like to send one&lt;/li&gt;
&lt;li&gt;  ChatMessage.vue - shows all chat messages in the group chat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parent component has quite a few regular methods as well, here's a break down of each:&lt;/p&gt;

&lt;p&gt;i) The &lt;code&gt;saveUsernameAndJoin()&lt;/code&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;saveUsernameAndJoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientUsername&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReadyToChat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatChannelInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;backgroundEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateBackgroundEventStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;join-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is invoked from the &lt;code&gt;UsernameInput.vue&lt;/code&gt; component and saves the username entered by the user. &lt;a href="https://ably.com/documentation/core-features/presence" rel="noopener noreferrer"&gt;Ably's Presence feature&lt;/a&gt; allows you to see the realtime connection status of any client. This is useful to see which users are currently online. We make this user enter the presence set with their username in this method. The &lt;code&gt;backgroundEventBus&lt;/code&gt; is a VueJS state management mechanism to emit various events to the infobox component.&lt;/p&gt;

&lt;p&gt;ii) The &lt;code&gt;handleNewMessage()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handleNewMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messageContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;msgTimestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;msgTimestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;msgType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;live&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;divScrollHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;divScrollHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myClientId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReadyToChat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;backgroundEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateBackgroundEventStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;live-msgs-loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Continuing from the channel subscription, this method is called for every new message pushed on chat the channel. We extract the required fields from the message and push it into the &lt;code&gt;chatMsgsArray&lt;/code&gt; which is used to display messages in the chat screen. This is a live msg (vs one that is retrieved from a database).&lt;/p&gt;

&lt;p&gt;iii) The &lt;code&gt;loadPreviousMsgs()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;loadPreviousMsgs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMsgsFromDBWithMsgID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestMsgsFromDB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is called when the 'load previous messages' popup is clicked. It checks if there is a previous message present in the chat array or not. Accordingly, calls other methods to retrieve messages from the database.&lt;/p&gt;

&lt;p&gt;iv) The &lt;code&gt;getMsgsFromDBWithMsgID&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getMsgsFromDBWithMsgID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latestMsgId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msgId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showLoadMoreBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showLoadMoreBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Airtable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;configVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AIRTABLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AIRTABLE_BASE_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Table 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grid view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filterByFormula&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEARCH('&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latestMsgId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;',{msgId})&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eachPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchNextPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latestRecordID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbAutoNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;latestRecordID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latestRecordID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMsgsFromDBWithAutoID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;fetchNextPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is invoked when there's a previous message present in the array. Note that all the records in the database are pre-ordered chronologically with an auto incrementing ID field. We use the &lt;code&gt;msgId&lt;/code&gt; of the earliest message to find that record's ID in the Airtable database, then send another request to retrieve three records with ID less than the previously retrieved record's ID. This is done in the &lt;code&gt;getMsgsFromDbWithAutoID()&lt;/code&gt; method added next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getMsgsFromDBWithAutoID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Table 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;maxRecords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grid view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filterByFormula&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;({ID}&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbAutoNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eachPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchNextPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;msgTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;msgType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="nx"&gt;backgroundEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateBackgroundEventStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db-msgs-loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nf"&gt;fetchNextPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add each of the retrieved records at the front of the &lt;code&gt;chatsMsgsArray&lt;/code&gt; so they appear at the top of the chat list in the UI as the messages are ordered chronologically.&lt;/p&gt;

&lt;p&gt;v) The &lt;code&gt;getLatestMsgsFromDB()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getLatestMsgsFromDB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Airtable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;configVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AIRTABLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AIRTABLE_BASE_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Table 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;maxRecords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grid view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eachPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchNextPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;messageContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;msgTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;msgType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="nx"&gt;backgroundEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateBackgroundEventStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db-msgs-loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;vueContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMsgsBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nf"&gt;fetchNextPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is invoked if there were no messages in the &lt;code&gt;chatMsgsArray&lt;/code&gt;, meaning there was no earliest record to reference. We simply need the last three messages available in the database. The previous option can be combined with this as the &lt;code&gt;filterByFormula&lt;/code&gt; field is the only differentiator, but it's added in two separate methods to make the two cases evidently clear.&lt;/p&gt;

&lt;p&gt;3. &lt;code&gt;ChatInput.vue&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As mentioned before, this method manages the input box to add a new chat message. It has a single method that is invoked when the send button is clicked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;publishMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myMessageContent&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uniqueMsgId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msgPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;msgId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uniqueMsgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientUsername&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat-message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myMessageContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatChannelInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msgPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;backgroundEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateBackgroundEventStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publish-msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myMessageContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, we compute a random (unique) id to assign to the message and publish it on the chat channel with the message copy and other information like the clientId and username. As the &lt;code&gt;echoMessages&lt;/code&gt; &lt;a href="https://ably.com/documentation/realtime/usage#client-options" rel="noopener noreferrer"&gt;Ably client option&lt;/a&gt; is turned off by default, the same client also receives this message as a subscription update on the channel, leading to that message being added to the array and ultimately appearing in the UI.&lt;/p&gt;

&lt;p&gt;As the &lt;code&gt;UsernameInput.vue&lt;/code&gt;and &lt;code&gt;ChatMessage.vue&lt;/code&gt; components are pretty much self-explanatory with minor data transformation and display, we'll skip explanations on those.&lt;/p&gt;

&lt;p&gt;With that, we've closed the full loop of data transfer from publisher to subscriber to the database and back to the subscriber. Here's the link again to the live demo so you can check it out again and piece the above information together: &lt;a href="https://realtime-chat-storage.ably.dev/" rel="noopener noreferrer"&gt;https://realtime-chat-storage.ably.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F12%2FScreenshot-2020-12-03-at-10.20.05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F12%2FScreenshot-2020-12-03-at-10.20.05.png" alt="Group chat app with Ably and Airtable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Group chat app with Ably and Airtable&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring other ways to get data from Airtable into Ably
&lt;/h2&gt;

&lt;p&gt;You might say it's all working fine, why explore of other ways? While we can publish messages directly into Airtable and retrieve those message again from the front-end app, we have a few gaps in this project stopping it from being production-ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if for whatever reason, someone adds a message in Airtable?&lt;/strong&gt; We won't be able to show those new messages in the chat app until the whole thing is refreshed and refreshing is not fun and a no-go when dealing with realtime data. While Airtable is not a realtime database i.e. it doesn't push any changes out, we have a work-around to this problem. Enter, Zapier!&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Zapier and Ably to convert Airtable into a realtime database (well, kind of)
&lt;/h3&gt;

&lt;p&gt;Zapier is a workflow management application which connects two or more SaaS platforms to share event-driven data. We can connect Airtable and Ably on Zapier and have it publish a message to a given Ably channel when a new record is added in the Airtable database. It would like something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2FScreenshot-2020-11-30-at-16.54.29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.ably.io%2Fghost%2Fprod%2F2020%2F11%2FScreenshot-2020-11-30-at-16.54.29.png" alt="Using Zapier and Ably to convert Airtable into a realtime database"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can decide if you want these updates published on the same chat channel or a different one and manage those updates accordingly. A quick note here is that you can publish different events on the same Ably channel to differentiate different types of updates. You can &lt;a href="https://ably.com/documentation/realtime/channels#publish" rel="noopener noreferrer"&gt;learn more about event name object in the publish method in Ably docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacing REST requests with GraphQL
&lt;/h3&gt;

&lt;p&gt;If you followed through the explanation for the chat app, you know that if we want to retrieve messages from a given point in the database, we'll need to send two subsequent requests to get the actual required data. Don't worry if you skipped through that entire section, I understand it was long :) You can just look for the phrase &lt;code&gt;getMsgsFromDBWithMsgID&lt;/code&gt; and you'll land in the right section that I'm referring to here.&lt;/p&gt;

&lt;p&gt;We can optimize that process by replacing the REST requests with the popular kid on the block - &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt;! While it's not supported officially by Airtable, &lt;a href="https://github.com/thomascullen/airtable-graphql" rel="noopener noreferrer"&gt;Thomas Cullen&lt;/a&gt; built a GraphQL plugin for Airtable as a community contributed project and it's perfect for this scenario. You can &lt;a href="https://www.npmjs.com/package/airtable-graphql" rel="noopener noreferrer"&gt;check it out on NPM&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing it up...
&lt;/h2&gt;

&lt;p&gt;Airtable and Ably are great services with just the right level of flexibility in terms of usage. We saw how to publish and subscribe to realtime messages using Ably and automatically have those messages stored in Airtable in realtime using the WebHooks Integrations feature.&lt;/p&gt;

&lt;p&gt;We also saw how to retrieve just the required messages from Airtable and display them for the user. We further explored other options of retrieving and publishing data to Airtable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Live demo: &lt;a href="https://realtime-chat-storage.ably.dev/" rel="noopener noreferrer"&gt;https://realtime-chat-storage.ably.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub project: &lt;a href="https://github.com/ably-labs/ably-airtable-starter-kit" rel="noopener noreferrer"&gt;https://github.com/ably-labs/ably-airtable-starter-kit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hope this post was useful. If you are building something with Airtable and Ably, I'd love to see your project and give it a shoutout. And of course happy to help you with any questions or concerns. You can raise them at &lt;a href="mailto:devrel@ably.com"&gt;devrel@ably.com&lt;/a&gt; or &lt;a href="https://twitter.com/Srushtika" rel="noopener noreferrer"&gt;DM me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>database</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A scalable, realtime quiz framework to build EdTech apps</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Fri, 23 Oct 2020 15:38:41 +0000</pubDate>
      <link>https://dev.to/ably/a-scalable-realtime-quiz-framework-to-build-edtech-apps-5394</link>
      <guid>https://dev.to/ably/a-scalable-realtime-quiz-framework-to-build-edtech-apps-5394</guid>
      <description>&lt;p&gt;Hello developers!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR&lt;/p&gt;

&lt;p&gt;I built a realtime quiz framework so developers building EdTech apps can quickly get started with scalable realtime messaging and focus on their app logic. It is a fully customizable framework built with NodeJS, VueJS, and powered by Ably's realtime infrastructure which primarily operates on WebSockets. &lt;/p&gt;

&lt;p&gt;It also implements Node JS worker threads to simulate  multiple 'quiz rooms' (aka dedicated servers spooled up on-demand) so different groups of people can simultaneously participate in different live quizzes organized by their dedicated hosts. You can check out the framework on GitHub at &lt;a href="https://github.com/Srushtika/realtime-quiz-framework"&gt;https://github.com/Srushtika/realtime-quiz-framework&lt;/a&gt; or try out the live demo at &lt;a href="https://quiz.ably.dev/"&gt;https://quiz.ably.dev/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is EdTech?
&lt;/h2&gt;

&lt;p&gt;One of the positive outcomes of this new world we are living in, is the rise of &lt;a href="https://ably.com/solutions/edtech"&gt;EdTech&lt;/a&gt;. Educational technology (EdTech) is the combined use of computer hardware, software, and educational theory and practice to facilitate learning. The origins of EdTech can be traced back to the late 90's and it has grown slowly but steady year after year, but it all changed in 2020 as 1.57 billion learners in 190 countries moved from classrooms to interactive, digital environments during Covid-19. This has accelerated EdTech’s existing growth, driving transformation that’s here to stay.&lt;/p&gt;

&lt;p&gt;This incredible growth didn't come without new demands and requirements of innovation, and, a stepping stone to innovation is realtime communication: between students and teachers for synchronous learning, and between devices and the cloud for safety, security, and cheating prevention.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a realtime quiz framework can help EdTech apps
&lt;/h2&gt;

&lt;p&gt;Most of the EdTech tools and platforms have live collaboration features. If you think about, most of the collaborative realtime apps follow a common pattern and feature set, with customizations for their specific scenario.&lt;/p&gt;

&lt;p&gt;For a live quiz, which can double up as a test-taking app for a class of high schoolers, or simply a movie trivia for a fun virtual Pub Quiz Friday with your workmates, there are a lot of common patterns without even needing much customization.&lt;/p&gt;

&lt;p&gt;For example, they need to be able to create their own private quiz room and invite other people to that room. In most cases, one of the participants needs to have special admin controls (mostly the host). They need to be able to easily control the progression of the quiz. And so on...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6w8d44bnsvlvdck4rsnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6w8d44bnsvlvdck4rsnp.png" alt="A screenshot of the admin and player apps in the framework" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keeping in mind these commonalities and to avoid the need for developers working on EdTech platforms to build everything from scratch, I built an open-sourced realtime quiz framework to quickly get a working quiz/ test-taking platform up and running in no time. Developers can easily customize this extensible framework as per their specific use-cases and scenarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to skip the details and take a look at the live demo, check it out at &lt;a href="https://quiz.ably.dev/"&gt;https://quiz.ably.dev/&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Some background before I jump into the details
&lt;/h2&gt;

&lt;p&gt;In the last few months, I worked on a few browser games and realized there were common patterns in all these realtime games that needed continuous high-speed streaming of data between the players and the game server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4vorevuryq2ndjpq4xn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4vorevuryq2ndjpq4xn5.png" alt="An image of the Flappy Bird multiplayer game built over Websockets using Ably" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvklbd2pgkz8f686mo6m9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvklbd2pgkz8f686mo6m9.png" alt="An image of the multiplayer Space Invaders game built over Websockets using Ably" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture was similar but the type of input from the players, and the game logic on the server changed according to the game in question. So I put all these observations together and built an arbitrarily scaling networking framework to build realtime multiplayer games. This proved to be useful to a bunch of people to set up realtime networking in no time and focus on customizing it per the game logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fti198b2azkiwybkn14h6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fti198b2azkiwybkn14h6.png" alt="An image of the GitHub repo of the scalable networking framework for games" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I moved on from games, I did some research to see 'what's hot' currently and, as depressing are a lot of events around the world this year, a super positive and refreshing trend has been an increase in virtual live collaboration. This led me to think about e-learning in terms of various institutions conducting their classes and tests online. So I played around with a bunch of such EdTech platforms, and again, observed a lot of common patterns everywhere. I put these observations together to build this &lt;a href="https://github.com/Srushtika/realtime-quiz-framework"&gt;extensible realtime quiz framework&lt;/a&gt;. I hope this is a useful starting point if you are wanting to build an EdTech tool of any kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what's this framework all about?
&lt;/h2&gt;

&lt;p&gt;The realtime quiz framework is a fully open-sourced full-stack project built with &lt;strong&gt;Node JS&lt;/strong&gt; and &lt;strong&gt;Vue JS&lt;/strong&gt; and the architecture consists of the &lt;a href="https://ably.com/topic/websockets"&gt;Websockets protocol&lt;/a&gt; and the &lt;a href="https://ably.com/topic/pub-sub"&gt;Publish/Subscribe messaging pattern&lt;/a&gt;. It's a working app with the basic functionality required to build a realtime quiz app.&lt;/p&gt;

&lt;p&gt;To be honest, calling it a quiz app is a bit generic as the architecture would be the same for a test-taking app for an institution, a &lt;a href="https://en.wikipedia.org/wiki/HQ_(video_game)"&gt;HQ style live trivia app&lt;/a&gt;, or a more one to one quiz like &lt;a href="https://www.quizup.com/en"&gt;Quiz Up&lt;/a&gt;. As the framework is using &lt;a href="https://ably.com/documentation/realtime/"&gt;Ably's Realtime infrastructure&lt;/a&gt; for realtime messaging between various components, it is readily scalable to an enterprise level.&lt;/p&gt;

&lt;p&gt;The framework shows two ways of conducting a quiz.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcs6csw2mzixyj7dem8es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcs6csw2mzixyj7dem8es.png" alt="A screenshot of the live quiz application" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can have the questions stored in your server or have the end-users easily upload their own questions by sharing a link to their Google Sheet. You can easily extend this to attach a database for a more persisted data storage and allow a greater range of already available quizzes to select from.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgchmwr62j37eewmbmi38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgchmwr62j37eewmbmi38.png" alt="A screenshot of the live quiz application" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is that the host of the quiz and other players are already on a video call (on another platform). The host shares their screen invites other players to their quiz using a shareable link, and manages the progression of the quiz throughout. They'll be able to see the players with dummy avatars but real nicknames show up in a list as they join.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdqs2n5b3zds3y0jztb3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdqs2n5b3zds3y0jztb3k.png" alt="A screenshot of the live quiz application" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the host feels that the expected players have joined, they can start the quiz. After a short timeout, the questions start showing up. The players can see the question, an image if available, and four options. The four options are buttons and the players can choose one of them to register their answer for that question. The interface is a bit different for the host in that they'll also see the question, optional image and four options but these are not clickable as the host is essentially not participating in the quiz themselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frr2sfn4n53axkppgdkmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frr2sfn4n53axkppgdkmd.png" alt="A screenshot of the live quiz application in action" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to this, the host also sees a live stats panel which shows the number of players online and out of those the number of players who've already answered that question. You can easily extend this to show the names and avatars of those players or any other live stats like the high score etc.&lt;/p&gt;

&lt;p&gt;Each question shows up for 30 seconds but if all the players have answered the question, the rest of the timer is skipped. One interesting thing to note about the timer is that every second update in the timer is a result of the data coming in from the server-side. This not only ensures that end-users have no way to tweak the timers and such in the client-side app, but also that all the participants in the quiz along with the host are fully in-sync. After a question's time has elapsed, the leaderboard info so far can be seen on the host app. The host then has an option to show the next question or end the quiz midway through. If you think of a pub quiz scenario where after each question, you have a random banter about how people thought their answers were correct, this gives the proper control to the host to allow time for that banter and show the next question only when everyone is ready.&lt;/p&gt;

&lt;p&gt;In terms of the answer after each question, the host can see on their screen the correct answer to the previously displayed question. The players, in addition to being able to see the correct answer, will be prompted if the answer they chose was correct or wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwdfu4tel2z9xdjgj1094.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwdfu4tel2z9xdjgj1094.png" alt="A screenshot of the live quiz application" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After all the questions in the chosen quiz have finished playing, a final leaderboard containing the scores of all the participants will be displayed on the host app.&lt;/p&gt;

&lt;p&gt;As this is a base framework, each component can be easily customized! The framework is available on &lt;a href="https://github.com/Srushtika/realtime-quiz-framework"&gt;GitHub&lt;/a&gt; along with a detailed &lt;a href="https://github.com/Srushtika/realtime-quiz-framework/blob/main/README.md"&gt;README.md&lt;/a&gt; on getting it working and customizing it. It also has a &lt;a href="https://github.com/Srushtika/realtime-quiz-framework/blob/main/TUTORIAL.md"&gt;TUTORIAL.md&lt;/a&gt; if you'd like to understand in much more detail what's happening in every method of the app.&lt;/p&gt;

&lt;p&gt;The live demo is also available at &lt;a href="https://quiz.ably.dev/"&gt;https://quiz.ably.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this framework is useful for your realtime EdTech adventures or anything else and I can't wait to see what you build with it! Please feel free to share it with me and Ably on Twitter and we'll be happy to give it a shoutout.&lt;/p&gt;

&lt;p&gt;If you have any questions, feel free to &lt;a href="https://twitter.com/Srushtika"&gt;DM me on Twitter&lt;/a&gt; or reach out to the &lt;a href="https://ably.com/support"&gt;support team at Ably&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stay home, stay safe, and have fun virtually!&lt;/p&gt;




&lt;p&gt;Header image illustration credits: &lt;a href="https://www.freepik.com/vectors/school"&gt;School vector created by pch.vector - www.freepik.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Presenting a realtime communication framework to build multiplayer games</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Fri, 04 Sep 2020 07:26:25 +0000</pubDate>
      <link>https://dev.to/ably/building-realtime-multiplayer-games-has-never-been-easier-presenting-a-realtime-communication-framework-for-games-1lk7</link>
      <guid>https://dev.to/ably/building-realtime-multiplayer-games-has-never-been-easier-presenting-a-realtime-communication-framework-for-games-1lk7</guid>
      <description>&lt;p&gt;Hello game developers!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR - I built a realtime communication framework so game developers can build multiplayer games without needing to worry about the networking side of it and instead focus on their game logic. It is powered by &lt;a href="https://ably.com/" rel="noopener noreferrer"&gt;Ably's realtime infrastructure&lt;/a&gt; which primarily operates on &lt;a href="https://ably.com/topic/websockets" rel="noopener noreferrer"&gt;WebSockets&lt;/a&gt; and is built in Node JS and Vanilla JS (use it with any JS framework you like 😉). It also implements Node JS worker threads to simulate multiple 'game rooms' (aka dedicated server spooled up on-demand) so different groups of players can simultaneously play the game. You should check it out: &lt;a href="https://github.com/Srushtika/multiplayer-games-scalable-networking-framework" rel="noopener noreferrer"&gt;https://github.com/Srushtika/multiplayer-games-scalable-networking-framework&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Faxb5a5lkd3p1an1lp3i3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Faxb5a5lkd3p1an1lp3i3.gif" alt="Demo application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few months back I got into the world of building high-frequency (realtime) multiplayer games to get my boss's attention as he's extremely passionate about game development (Challenge: Try to have a tech conversation with &lt;a href="https://twitter.com/BenGamble7/" rel="noopener noreferrer"&gt;Ben Gamble&lt;/a&gt;, without game dev coming up!🌚🕹). &lt;/p&gt;

&lt;p&gt;When I started, I had so many misconceptions about game dev. I underestimated certain aspects and had misconceptions about scalable game architectures. But I've learned so much about game dev since then and have built multiplayer versions of a few classics such as &lt;a href="https://dev.to/ablydev/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-1-4-14pm"&gt;Multiplayer Space Invaders&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=ReGHyTh1ydU" rel="noopener noreferrer"&gt;Multiplayer Flappy birds&lt;/a&gt;. I've also written and spoken about game dev quite extensively on various platforms. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffahusd8fhhqq402bqnyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffahusd8fhhqq402bqnyu.png" alt="Multiplayer space invaders presented at HalfStack online conference, 2020"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7e5ovf9nizoob2mczb57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7e5ovf9nizoob2mczb57.png" alt="Multiplayer Flappy Birds, live gameplay on YouTuber Eddie Jaoude's channel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each such presentation, I made improvements to the games by not only making them more efficient but also having them use the best and latest features of the underlying programming language. For example, I used &lt;a href="https://nodejs.org/api/worker_threads.html" rel="noopener noreferrer"&gt;Node JS worker threads&lt;/a&gt; (which was released as a stable version in January 2020) to simulate the idea of having multiple game rooms so different groups of people can play the game simultaneously. This is analogous to spooling up on-demand dedicated servers for each game. &lt;a href="https://support.ably.com/support/solutions/folders/3000005541" rel="noopener noreferrer"&gt;Ably's namespace feature&lt;/a&gt; made this strategy possible by ensuring none of the rooms had any access to the data from the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  More games = repeating boilerplate? 🤯🥵
&lt;/h2&gt;

&lt;p&gt;While I was continuing to think of more multiplayer game ideas that were cool enough to convince my team to let me work on them during Ably working hours 🌚, I realized some commonalities in all these projects:&lt;br&gt;
All of my multiplayer games so far follow the &lt;a href="https://www.gabrielgambetta.com/client-server-game-architecture.html" rel="noopener noreferrer"&gt;Client/Server game strategy&lt;/a&gt;. Hence, basic networking architecture is exactly the same and has the following aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The server maintains the game state and publishes it at high frequency to a &lt;a href="https://ably.com/documentation/realtime/channels" rel="noopener noreferrer"&gt;channel&lt;/a&gt; that all the players are subscribed to. This ensures all players are in-sync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each player sends their current state (based on user input, etc) on a unique channel meant for that client. The server is subscribed to this and uses this info to maintain the up to date game state mentioned above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The game has multiple game rooms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The host player has options to start and end the game.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In general, there's awareness of every player's score, position, alive/dead status, join/ leave updates for the game, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5kmj63zz5byjefct2lpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5kmj63zz5byjefct2lpj.png" alt="Client/Server architecture with each color representing a realtime channel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Come to think about it, it seems that this is pretty much what most real-time multiplayer games would need. So, I thought building a networking framework would benefit game developers to quickly add the multiplayer functionality to any game. All the more useful when it's built with Ably, as it means you can easily scale it and have access to so many other realtime communication features (like message ordering, availability, guaranteed message delivery, etc) So, here it is!&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiplayer games scalable networking framework
&lt;/h2&gt;

&lt;p&gt;This framework serves as a starter kit that allows you to add multiplayer functionality (that follows the &lt;a href="https://www.gabrielgambetta.com/client-server-game-architecture.html" rel="noopener noreferrer"&gt;Client/Server strategy&lt;/a&gt;  to your game. It provides a communication framework so that your players can communicate with a central server, in realtime, for the entire duration of the gameplay.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqaxsx6eeovi1ojxtogpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqaxsx6eeovi1ojxtogpa.png" alt="Homepage of the demo app that works on this framework"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also allows you to implement a 'game rooms' feature using Node JS  worker threads, which is analogous to spooling up dedicated servers on-demand, allowing you to spin up multiple instances of the game,  each with a separate group of players. It comes with a skeleton demo app where this game functionality can be simulated. The idea is for game developers to take this framework and add their game logic to make it their own!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff8f7l4ft56ajy31vbd31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff8f7l4ft56ajy31vbd31.png" alt="Host waiting area of the demo app that works on this framework"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  You can find the full project with a detailed guide on how to use it on GitHub: &lt;a href="https://github.com/Srushtika/multiplayer-games-scalable-networking-framework" rel="noopener noreferrer"&gt;https://github.com/Srushtika/multiplayer-games-scalable-networking-framework&lt;/a&gt;
&lt;/h3&gt;




&lt;p&gt;If you have any questions/ suggestions, etc. Feel free to shoot an email to &lt;a href="//mailto:devrel@ably.io"&gt;devrel@ably.io&lt;/a&gt; or reach out to me directly on &lt;a href="https://www.twitter.com/Srushtika" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, I'll be so glad to talk about this 😃 &lt;/p&gt;

&lt;p&gt;If you like it, star it ⭐️ and don't forget to share any multiplayer games you build with this, I'd love to check them out and give a shoutout! 🥇🚀🔥&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>node</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a realtime multiplayer browser game in less than a day - Part 4/4</title>
      <dc:creator>Srushtika Neelakantam</dc:creator>
      <pubDate>Fri, 12 Jun 2020 10:04:20 +0000</pubDate>
      <link>https://dev.to/ably/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-4-4-3d2j</link>
      <guid>https://dev.to/ably/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-4-4-3d2j</guid>
      <description>&lt;p&gt;Hello, and welcome to the final part of this article series where we are looking at the step-by-step implementation of a realtime multiplayer game of space invaders with &lt;a href="https://phaser.io/" rel="noopener noreferrer"&gt;Phaser3&lt;/a&gt; and &lt;a href="https://ably.com/" rel="noopener noreferrer"&gt;Ably Realtime&lt;/a&gt;. 🚀&lt;/p&gt;




&lt;p&gt;Here's the full index of all the articles in this series for context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="https://dev.to/srushtika/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-1-4-14pm"&gt;Introduction to gaming concepts and the Phaser library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="https://dev.to/ablydev/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-2-4-1p1l"&gt;Evaluating networking protocols for realtime apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href="https://dev.to/ablydev/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-3-4-4bbm"&gt;Implementing the server-side code to keep all players in sync&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: &lt;a href="https://dev.to/ablydev/building-a-realtime-multiplayer-browser-game-in-less-than-a-day-part-4-4-3d2j"&gt;Finishing up the client-side code to render the game&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;In this article, we'll finish up the client-side code to render the game and  also add the home and leaderboard screens for our game.&lt;/p&gt;

&lt;p&gt;If you recall, in the first article we added the &lt;code&gt;GameScene&lt;/code&gt; class and defined the &lt;code&gt;preload()&lt;/code&gt; method in it. We also added the &lt;code&gt;create()&lt;/code&gt; and &lt;code&gt;update()&lt;/code&gt; methods but didn't define them fully.&lt;/p&gt;

&lt;p&gt;Let's start by adding some variables that we'll use later. Add these at the top of &lt;code&gt;script.js&lt;/code&gt; (which should be inside the &lt;code&gt;public&lt;/code&gt; folder:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Make sure to update the &lt;code&gt;BASE_SERVER_URL&lt;/code&gt; with your server's URL. If you have hosted the game locally, this URL would be your localhost with the port number. &lt;/p&gt;

&lt;p&gt;Next, we'll have the client connect to &lt;a href="https://ably.com/" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; and subscribe to the channels. To do that, go to the &lt;a href="https://ably.com/dashboard" rel="noopener noreferrer"&gt;Ably dashboard&lt;/a&gt; and add the following code, right below the variable declarations in &lt;code&gt;script.js&lt;/code&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;One of the key things to note here is the &lt;code&gt;gameRoom.presence.enter(myNickname);&lt;/code&gt; method. Ably uses a concept called &lt;a href="https://ably.com/documentation/core-features/presence" rel="noopener noreferrer"&gt;Presence&lt;/a&gt; to determine connected clients in an app. It fires an event whenever a new client joins, or when an existing client leaves or updates their data.&lt;/p&gt;

&lt;p&gt;Notice that this is where we instantiate a new game object with the &lt;code&gt;GameScene&lt;/code&gt; that we started defining in the first part. So, let's resume that. The &lt;code&gt;create()&lt;/code&gt; method of the class should now look as follows:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We had already defined the &lt;code&gt;this.anims.create()&lt;/code&gt;method in the first article. Just above that, we add and initialize a few variables. We then subscribe to the &lt;code&gt;game-state&lt;/code&gt; and &lt;code&gt;game-over&lt;/code&gt; events on the &lt;code&gt;gameRoom&lt;/code&gt; channels.&lt;/p&gt;

&lt;p&gt;When we get a &lt;code&gt;game-state&lt;/code&gt; update, we update the client-side variables as per the latest info from the server. &lt;/p&gt;

&lt;p&gt;When we get a &lt;code&gt;game-over&lt;/code&gt; update, we store the leaderboard info in local storage. We then unsubscribe the client from all channels and simply switch to a new webpage because either someone won or all players became dead.&lt;/p&gt;

&lt;p&gt;Let's look at the &lt;code&gt;update()&lt;/code&gt; method next:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the update method, we move the existing game objects in accordance with the latest info. We also create new avatars for the newly joined players, and kill avatars of any player that has died by calling the &lt;code&gt;explodeAndKill()&lt;/code&gt; method. We also update the score, and flash the join and leave updates in the &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; elements outside the game canvas.&lt;/p&gt;

&lt;p&gt;If the server says a player just died, we call the &lt;code&gt;explodeAndKill()&lt;/code&gt; method that will perform the explode animation and destroy that player's avatar.&lt;/p&gt;

&lt;p&gt;A bullet gets fired once for every five game ticks. So the server either sends a blank or a bullet object, with a unique ID and a position that matches the ship's y-axis level. If it hasn't already been shot, its &lt;code&gt;toLaunch&lt;/code&gt; flag will be true. So we check that and create a new bullet by calling the &lt;code&gt;createBullet()&lt;/code&gt; method. Otherwise, we'll move an existing one.&lt;/p&gt;

&lt;p&gt;We also check if the player has pressed the left of right keys via the &lt;code&gt;publishMyInput()&lt;/code&gt; method. &lt;/p&gt;

&lt;p&gt;Let's define these methods next. Please note that these methods are part of the &lt;code&gt;GameScene&lt;/code&gt; class.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;createBullet()&lt;/code&gt; method, we add a new bullet object according to the latest position of the ship and add this bullet to the &lt;code&gt;visibleBullets&lt;/code&gt; associative array that's part of the &lt;code&gt;GameScene&lt;/code&gt; class. We also add an overlap method for the current player's avatar and every bullet we add. This method will keep track of the overlap of the two game objects overlapping. When that occurs, Phaser will invoke a callback method, which in this case is  &lt;code&gt;publishMyDeathNews()&lt;/code&gt;. We'll define that later.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;publishMyInput()&lt;/code&gt; method, we check if the left or right key was pressed, and if yes, publish that info to Ably. It's worth noting here that we never move the avatars directly as a result of user input. We publish this info to the server, which in turn fans it out to all the players, including the current player, resulting in a perfect state synchronisation. This communication happens so fast that it doesn't really feel any different to the user playing the game.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;explodeAndKill()&lt;/code&gt; method,  we create a new instance of the &lt;code&gt;Explosion&lt;/code&gt; class. We haven't defined that yet, so let's take a brief detour from the &lt;code&gt;script.js&lt;/code&gt; file that we've been working on, to add it. Create a new file in the &lt;code&gt;public&lt;/code&gt; folder, call it &lt;code&gt;explosion.js&lt;/code&gt; and paste the following code in it.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This class extends &lt;code&gt;Phaser.GameObjects.Sprite&lt;/code&gt; and plays the &lt;code&gt;explode&lt;/code&gt; animation that we defined in the &lt;code&gt;create()&lt;/code&gt; method of our &lt;code&gt;GameScene&lt;/code&gt; class in the &lt;code&gt;script.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Now let's get back to &lt;code&gt;script.js&lt;/code&gt; and define one last method within the &lt;code&gt;GameScene&lt;/code&gt; class, the &lt;code&gt;publishMyDeathNews()&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This method is invoked when a bullet object overlaps with the current player's avatar, meaning the player has been shot. When that happens, we simply publish this information to the server so it can update the game state accordingly and fan this information out to all the clients, including the current player, so they can update their respective game states accordingly.&lt;/p&gt;

&lt;p&gt;We are all done with the game implementation. We just have to add the home and leaderboard pages to make the game more complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the home and leaderboard pages
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;views&lt;/code&gt; folder, add four files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gameRoomFull.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;code&gt;intro.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;code&gt;winner.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gameover.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwycswh2bama6t71y2zvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwycswh2bama6t71y2zvv.png" alt="Homepage screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fybw1xwmhcrsl5amily78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fybw1xwmhcrsl5amily78.png" alt="Winner screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;public&lt;/code&gt; folder, add three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nickname.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;code&gt;winner.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gameover.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;gameRoomFull.html&lt;/code&gt; is displayed when anyone tries to join the game after the preset maximum number of players have already joined. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;intro.html&lt;/code&gt; file gives the user a simple text box to enter their nickname. This info is used to flash join/leave updates and also show the info in the leaderboard.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;winner.html&lt;/code&gt; page is shown if the game ends due to a player winning the game. This page will then display their nickname as the winner and also show the first and second runners up.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;gameover.html&lt;/code&gt; page is shown if all the players in the game die. This page just shows the nicknames of the top two scorers.&lt;/p&gt;

&lt;p&gt;The related JavaScript files simply retrieve the info from the local storage and set it in the relevant HTML elements.&lt;/p&gt;




&lt;p&gt;That's it, we've now full implemented the game 🙌🏽🙌🏽🙌🏽&lt;/p&gt;

&lt;p&gt;Let's go ahead and run it. We first need to run the server, so from your command line, navigate to the folder where the server file is and run &lt;code&gt;node server.js&lt;/code&gt;. This will start the server. Now, open three browser windows and keep them side-by-side. Hit the base URL of your server from all the three windows. You should see the &lt;code&gt;intro.html&lt;/code&gt; page being served asking for a nickname. Give each player a nickname and enter. After the third player enters, the ship starts with the bullets going off. Make sure to control each player to avoid getting killed.&lt;/p&gt;

&lt;p&gt;If it's running as expected, you can host this game using a free hosting service such as &lt;a href="https://heroku.com" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; or &lt;a href="https://glitch.com/" rel="noopener noreferrer"&gt;Glitch&lt;/a&gt;. This will allow you to access the game via a public URL letting you play the game for real with your friends on other computers.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://github.com/Srushtika/realtime-multiplayer-space-invaders/releases/tag/v1.0.1" rel="noopener noreferrer"&gt;separate release&lt;/a&gt; relevant to this tutorial is available on GitHub if you'd like to check it out.&lt;/p&gt;

&lt;p&gt;You can also &lt;a href="https://github.com/Srushtika/realtime-multiplayer-space-invaders" rel="noopener noreferrer"&gt;follow the Github project for latest developments&lt;/a&gt; on this project.&lt;/p&gt;




&lt;p&gt;As always, if you have any questions, please feel free to reach out to me on Twitter &lt;a href="https://twitter.com/Srushtika/" rel="noopener noreferrer"&gt;@Srushtika&lt;/a&gt;. My DMs are open :)&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
