<?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>Forem: Jo Stichbury</title>
    <description>The latest articles on Forem by Jo Stichbury (@stichbury).</description>
    <link>https://forem.com/stichbury</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%2F127622%2Ff1e89c81-41b2-4305-abb0-04ab0cb54591.jpeg</url>
      <title>Forem: Jo Stichbury</title>
      <link>https://forem.com/stichbury</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/stichbury"/>
    <language>en</language>
    <item>
      <title>Introducing your new team lead…Kedro</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Wed, 19 Apr 2023 12:59:06 +0000</pubDate>
      <link>https://forem.com/kedro/introducing-your-new-team-leadkedro-nhl</link>
      <guid>https://forem.com/kedro/introducing-your-new-team-leadkedro-nhl</guid>
      <description>&lt;p&gt;This post explains how Kedro can guide an analytics team to follow best practices and avoid technical debt.&lt;/p&gt;

&lt;p&gt;In a recent article, I explained that &lt;a href="https://towardsdatascience.com/five-software-engineering-principles-for-collaborative-data-science-ab26667a311"&gt;following software principles can help you create a well-ordered analytics project&lt;/a&gt; to share, extend and reuse in the future. In this post we'll review how you can benefit from using Kedro as a toolbox to apply best practices to data science code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How data science projects fail
&lt;/h2&gt;

&lt;p&gt;As data scientists, we aspire to unlock valuable insights by building&lt;br&gt;
well-engineered prototypes that we can take forward into production.&lt;br&gt;
Instead, there is a tendency for us to make poor engineering decisions&lt;br&gt;
in the face of tight deadlines or write code of dubious quality through&lt;br&gt;
a lack of expertise. &lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://www.splunk.com/en_us/data-insider/what-is-tech-debt.html"&gt;technical debt&lt;/a&gt; and prototype code that is difficult to understand,&lt;br&gt;
maintain, extend, and fix. Projects that once looked promising fail to transition past the experimental stage into production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A cycle of quick and exciting research leads to high expectations of&lt;br&gt;
great improvement, followed by a long series of delays and&lt;br&gt;
disappointments where frustrating integration work fails to recreate&lt;br&gt;
those elusive improvements, made all the worse by the feeling of sunk&lt;br&gt;
costs and a need to justify the time spent."&lt;/p&gt;

&lt;p&gt;Joe Plattenburg, Data Scientist at Root Insurance&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  How to write well-engineered data science code
&lt;/h2&gt;

&lt;p&gt;When you start to cut code on a prototype, you may not prioritize&lt;br&gt;
maintainability and consistency. Adopting a team culture and way of&lt;br&gt;
working to minimize technical debt can make the difference between&lt;br&gt;
success and failure.&lt;/p&gt;

&lt;p&gt;Some of the most valuable techniques a data scientist can pick up are&lt;br&gt;
those that generations of software engineers already use, such as the&lt;br&gt;
following guidelines:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a standard and logical project structure&lt;/strong&gt;: It is easier to&lt;br&gt;
understand a project, and share it with others, if you follow a standard&lt;br&gt;
structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't use hardcoded values&lt;/strong&gt;: instead, use precisely named constants&lt;br&gt;
and put them all into a single configuration file so you can find and&lt;br&gt;
update them easily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactor your code&lt;/strong&gt;: In data science terms, it often makes sense to&lt;br&gt;
use a Jupyter notebook for experimentation. But once your experiment is&lt;br&gt;
done, it's time to clean up the code to remove elements that make it&lt;br&gt;
unmaintainable, and to remove accidental complexity. Refactor the code&lt;br&gt;
into Python functions and packages to form a pipeline that can be&lt;br&gt;
routinely tested to ensure repeatable behaviour.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Testing after each change means that when I make a mistake, I only&lt;br&gt;
have a small change to consider in order to spot the error, which&lt;br&gt;
makes it far easier to find and fix."&lt;/p&gt;

&lt;p&gt;Martin Fowler, Author of Refactoring: Improving the Design of Existing&lt;br&gt;
Code&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Make code reusable by making it readable&lt;/strong&gt;: Write your pipelines as a&lt;br&gt;
series of small functions that do just one task, with single return&lt;br&gt;
paths and a limited number of arguments.&lt;/p&gt;

&lt;p&gt;Many data scientists say they've learned from their colleagues through&lt;br&gt;
pair programming, code reviews and in-house mentoring that enables them&lt;br&gt;
to build expertise suitable to their roles and requirements.&lt;/p&gt;

&lt;p&gt;We see Kedro as the always-available team lead that steers the direction&lt;br&gt;
of the analytics project from the outset and encourages use of a&lt;br&gt;
well-organized folder structure, software design that supports regular&lt;br&gt;
testing, and a culture of writing readable, clean code.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Kedro?
&lt;/h2&gt;

&lt;p&gt;Kedro is an open-source toolbox for production-ready data science. The&lt;br&gt;
framework was born at QuantumBlack to solve the challenges faced&lt;br&gt;
regularly in data science projects and promote teamwork through&lt;br&gt;
standardised team workflows. It is now hosted by the &lt;a href="https://lfaidata.foundation/"&gt;LF AI &amp;amp; Data&lt;br&gt;
Foundation&lt;/a&gt; as an incubating project.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/yEQqf3XUvzk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Kedro = Consistent project structure
&lt;/h3&gt;

&lt;p&gt;Kedro is built on the learnings of &lt;a href="https://drivendata.github.io/cookiecutter-data-science/"&gt;Cookie Cutter Data Science&lt;/a&gt;. It helps you to standardise how configuration, source&lt;br&gt;
code, tests, documentation, and notebooks are organised with an&lt;br&gt;
adaptable project template. If your team needs to build with multiple&lt;br&gt;
projects that have similar structure, you can also create your own&lt;br&gt;
Cookie Cutter project templates with Kedro starters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kedro = Maintainable code
&lt;/h3&gt;

&lt;p&gt;Kedro helps you refactor your business logic and data processing into&lt;br&gt;
Python modules and packages to form pipelines, so you can keep your&lt;br&gt;
notebooks clean and tidy.&lt;br&gt;
&lt;a href="https://demo.kedro.org"&gt;Kedro-Viz&lt;/a&gt; then visualises the pipelines to help you navigate .&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"People started from scratch each time, the same pitfalls were&lt;br&gt;
experienced independently, reproducibility was time consuming and only&lt;br&gt;
members of the original project team really understood each&lt;br&gt;
codebase...&lt;/p&gt;

&lt;p&gt;We needed to enforce consistency and software engineering best&lt;br&gt;
practices across our own work. Kedro gave us the super-power to move&lt;br&gt;
people from project to project and it was game-changing. After working&lt;br&gt;
with Kedro once, you can land in another project and know how the&lt;br&gt;
codebase is structured, where everything is and most importantly how&lt;br&gt;
you can help".&lt;/p&gt;

&lt;p&gt;Joel Schwarzmann, Principal Product Manager, QuantumBlack Labs, &lt;a href="https://medium.com/towards-data-science/five-software-engineering-principles-for-collaborative-data-science-ab26667a311"&gt;blog&lt;br&gt;
post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Kedro = Code quality
&lt;/h3&gt;

&lt;p&gt;Kedro makes it easy to avoid common code smells such as hard-coded&lt;br&gt;
constants and magic numbers. The configuration library enables your code&lt;br&gt;
to be reusable through data, model, and logging configuration. An&lt;br&gt;
ever-expanding data catalog supports multiple formats of data access.&lt;/p&gt;

&lt;p&gt;Kedro also makes it keep your code quality up to standard, through&lt;br&gt;
support for black, isort, and flake8 for code linting and formatting,&lt;br&gt;
pytest for testing, and Sphinx for documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kedro = Standardisation
&lt;/h3&gt;

&lt;p&gt;Kedro integrates with standard data science tools, such as TensorFlow,&lt;br&gt;
scikit-learn, or Jupyter notebooks for experimentation, and commonly&lt;br&gt;
used routes to deployment such as Databricks.&lt;/p&gt;

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

&lt;p&gt;Kedro is an open-source Python toolbox that applies software engineering&lt;br&gt;
principles to data science code. It makes it easier for a team to apply&lt;br&gt;
software engineering principles to data science code, which reduces the&lt;br&gt;
time spent rewriting data science experiments so that they are fit for&lt;br&gt;
production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you follow established best practice, you have a better chance of&lt;br&gt;
success.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software engineering principles only work if the entire team follows&lt;br&gt;
them. A tool like Kedro can guide you just like an experienced technical&lt;br&gt;
lead, making it second nature to use established best practices, and&lt;br&gt;
supporting a culture and set of processes based upon software&lt;br&gt;
engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Look forward to greater collaboration and productivity with Kedro in&lt;br&gt;
your team!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Find out more about Kedro
&lt;/h2&gt;

&lt;p&gt;There are many ways to learn more about Kedro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Join our &lt;a href="https://slack.kedro.org/"&gt;Slack organisation&lt;/a&gt; to reach out to us directly if you've a question or want to stay up to date with our news. There's an &lt;a href="https://www.linen.dev/s/kedro"&gt;archive of past past conversations on Slack&lt;/a&gt; too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.kedro.org/"&gt;Read our docs&lt;/a&gt; or look at the &lt;a href="https://github.com/kedro-org/kedro"&gt;Kedro source code on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check out our "&lt;a href="https://www.youtube.com/watch?v=NU7LmDZGb6E"&gt;Crash course in Kedro&lt;/a&gt; video on YouTube.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look out for an upcoming training session tailored to help your team get&lt;br&gt;
on-board with Kedro.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>kedro</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How do data scientists combine Kedro and Databricks?</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Wed, 19 Apr 2023 12:14:37 +0000</pubDate>
      <link>https://forem.com/kedro/how-do-data-scientists-combine-kedro-and-databricks-4pjd</link>
      <guid>https://forem.com/kedro/how-do-data-scientists-combine-kedro-and-databricks-4pjd</guid>
      <description>&lt;p&gt;In recent research, we found that Databricks is the dominant&lt;br&gt;
machine-learning platform used by Kedro users.&lt;/p&gt;

&lt;p&gt;The purpose of the research was to identify any barriers to using Kedro with Databricks; we are collaborating with the Databricks team to create a prioritised list of opportunities to facilitate integration. &lt;/p&gt;

&lt;p&gt;For example, Kedro is best used with an IDE, but IDE support on Databricks is still evolving, so we are keen to understand the pain points that Kedro users face when combining it with Databricks.&lt;/p&gt;

&lt;p&gt;Our research took qualitative data from 16 interviews, and quantitative data from a poll (140 participants) and a survey (46 participants) across the McKinsey and open-source Kedro user bases. We analysed two user journeys.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to ensure a Kedro pipeline is available in a Databricks workspace
&lt;/h2&gt;

&lt;p&gt;The first user journey we considered is how a user ensures the latest version of their pipeline codebase is available within the Databricks workspace. The most common workflow is to use Git, but almost a third of the users in our research set said there were a lot of steps to follow.&lt;/p&gt;

&lt;p&gt;The alternative workflow, which is to use dbx sync to Databricks repos, was used by less than 10% of the users we researched, indicating that awareness of this option is low.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ENvLTCgi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0r395hhea9dlgh3enl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ENvLTCgi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0r395hhea9dlgh3enl8.png" alt="Slide from presentation about Kedro and Databricks research" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run Kedro pipelines using a Databricks cluster
&lt;/h2&gt;

&lt;p&gt;The second user journey is how users run Kedro pipelines using a Databricks cluster. The most popular method, used by over 80% of participants in our research, is to use a Databricks notebook, which serves as an entry point to run Kedro pipelines. &lt;/p&gt;

&lt;p&gt;We discovered that many users were unaware of the IPython extension that significantly reduces the amount of code required to run Kedro pipelines in Databricks notebooks.&lt;/p&gt;

&lt;p&gt;We also found that some users run their Kedro pipelines by packaging them and running the resulting Python package on Databricks. However, Kedro did not support the packaging of configurations until version 18.5, which has caused problems. &lt;/p&gt;

&lt;p&gt;The final option some users select is to use Databricks Connect, but this is not recommended since it is soon&lt;br&gt;
to be sunsetted by Databricks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uMr4-AR---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrz30w22fdk36o1whzwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uMr4-AR---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrz30w22fdk36o1whzwn.png" alt="Slide from presentation about Kedro and Databricks research" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The output of our research
&lt;/h2&gt;

&lt;p&gt;To make it easier to pair Kedro and Databricks, we are updating Kedro's documentation to cover the latest Databricks features and tools, particularly the development and deployment workflows for Kedro on Databricks with DBx. The goal is to help Kedro users take advantage of the benefits of working locally in an IDE and still deploy to Databricks&lt;br&gt;
with ease.&lt;/p&gt;

&lt;p&gt;You can expect this new documentation to be released in the next one to two weeks.&lt;/p&gt;

&lt;p&gt;We will also be creating a Kedro Databricks plugin or starter project template to automate the recommended steps in the documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming soon...
&lt;/h2&gt;

&lt;p&gt;We have a managed Delta table dataset available in our Kedro datasets repo, which will be available for public consumption soon. We are also planning to support managed MLflow on Databricks.&lt;/p&gt;

&lt;p&gt;We have set up a &lt;a href="https://github.com/kedro-org/kedro/milestone/17"&gt;milestone on GitHub&lt;/a&gt; so you can check in on our progress and contribute if you want to. To suggest features to us, report bugs, or just see what we're working on right now, visit the Kedro projects on &lt;a href="https://github.com/kedro-org"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We welcome every contribution, large or small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find out more about Kedro
&lt;/h2&gt;

&lt;p&gt;There are many ways to learn more about Kedro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Join our &lt;a href="https://slack.kedro.org/"&gt;Slack organisation&lt;/a&gt; to reach out to us directly if you've a question or want to stay up to date with our news. There's an &lt;a href="https://www.linen.dev/s/kedro"&gt;archive of past past conversations on Slack&lt;/a&gt; too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.kedro.org/"&gt;Read our docs&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check out our "&lt;a href="https://www.youtube.com/watch?v=NU7LmDZGb6E"&gt;Crash course in Kedro&lt;/a&gt; video on YouTube.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look out for an upcoming training session tailored to help your team get on-board with Kedro.&lt;/p&gt;

</description>
      <category>python</category>
      <category>kedro</category>
      <category>databricks</category>
      <category>datascience</category>
    </item>
    <item>
      <title>A new home for the Kedro blog and some recent releases</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Tue, 04 Apr 2023 14:51:47 +0000</pubDate>
      <link>https://forem.com/kedro/a-new-home-for-the-kedro-blog-and-some-recent-releases-mg6</link>
      <guid>https://forem.com/kedro/a-new-home-for-the-kedro-blog-and-some-recent-releases-mg6</guid>
      <description>&lt;p&gt;In this post, we describe recent releases to Kedro, Kedro-Viz and some new Kedro datasets. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aYupLtp7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofli6panzt85bk3zs6qs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aYupLtp7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofli6panzt85bk3zs6qs.png" alt="Image description" width="592" height="592"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Kedro has a new blog over at &lt;a href="https://kedro.org/blog"&gt;kedro.org/blog&lt;/a&gt;! &lt;/p&gt;

&lt;p&gt;We’ve previously published on &lt;a href="https://medium.com/quantumblack" rel="noreferrer"&gt;QuantumBlack’s Medium channel&lt;/a&gt;, but recent updates and improvements here on the Kedro website mean that we’re now able to bring you a dedicated blog for, and about, the open-source Kedro community. &lt;/p&gt;
&lt;p&gt;We plan to publish a range of articles by contributors from within the team and beyond. If you’re a Kedroid with an idea for a post, please reach out to us using one of the channels on the &lt;a href="https://slack.kedro.org/" rel="noreferrer"&gt;Slack organisation&lt;/a&gt;, or &lt;a href="https://github.com/kedro-org/kedro-devrel/issues?q=is%3Aissue+is%3Aopen+label%3A%22blog+post%22" rel="noreferrer"&gt;raise an issue on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="kedro-releases"&gt;Kedro releases&lt;/h2&gt;
&lt;p&gt;We last gave an update on Kedro in late 2022, when &lt;a href="https://medium.com/quantumblack/the-latest-kedro-developments-9a4d15a7ceb5" rel="noreferrer"&gt;we described the features in Kedro version 0.18.4&lt;/a&gt;. Since then, we’ve released three additional non-breaking versions of Kedro in the 0.18.x series, with the goal of a regular release cadence at the end of most two-week development sprints.&lt;/p&gt;
&lt;p&gt;Some of the highlights of our releases are described below along with links to the full release notes. For each of these releases there’s a straightforward upgrade path with pip or conda. For example, to upgrade to Kedro version 0.18.7 from version 0.18.4:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip install kedro==0.18.7&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;p&gt;&lt;code&gt;conda install -c conda-forge kedro==0.18.7&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;We received many contributions to these new versions from our open-source community and want to thank every contributor for taking the time to extend and improve Kedro.&lt;/b&gt;&lt;/p&gt;
&lt;h3&gt;Kedro version 0.18.7 &lt;/h3&gt;
&lt;p&gt;These are the headline changes (You can find all the &lt;a href="https://github.com/kedro-org/kedro/releases/tag/0.18.7" rel="noreferrer"&gt;details about the Kedro 0.18.7 release&lt;/a&gt; on GitHub):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We added new Kedro CLI command &lt;code&gt;kedro jupyter setup&lt;/code&gt; to set up a Jupyter Kernel for Kedro that automatically loads the Kedro extension for ease of use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;kedro package&lt;/code&gt; command now includes the project configuration in a compressed &lt;code&gt;tar.gz&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We’ve added functionality to package and read your configuration as a compressed file. You can now use &lt;code&gt;OmegaConfigLoader&lt;/code&gt; to load configuration from compressed files of zip or tar format. (This feature requires &lt;code&gt;fsspec&amp;gt;=2023.1.0&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In documentation news, we moved seamlessly from &lt;code&gt;kedro.readthedocs.io&lt;/code&gt; to &lt;a href="https://docs.kedro.org/" rel="noreferrer"&gt;docs.kedro.org&lt;/a&gt; in this release. We also made some significant improvements to on-boarding documentation that covers setup for new Kedro users and major changes to the spaceflights tutorial to make it faster to work through. We think it’s a better read. &lt;a href="https://github.com/kedro-org/kedro/issues/new/choose" rel="noreferrer"&gt;Tell us if it’s not&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Kedro version 0.18.6&lt;/h3&gt;
&lt;p&gt;This was a small release to fix a bug introduced in Kedro 0.18.5 that was causing experiment tracking in Kedro-Viz to fail. You can find all the &lt;a href="https://github.com/kedro-org/kedro/releases/tag/0.18.6" rel="noreferrer"&gt;details about the release of Kedro version 0.18.6&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;h3&gt;Kedro version 0.18.5&lt;/h3&gt;
&lt;p&gt;In February 2023, we released Kedro version 0.18.5, to introduce a brand new config loader powered by &lt;a href="https://omegaconf.readthedocs.io/en/2.3_branch/" rel="noreferrer"&gt;OmegaConf&lt;/a&gt;. You can now use the &lt;code&gt;omegaconf&lt;/code&gt; syntax with &lt;code&gt;kedro run --param&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;We also added the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Some improvements to the &lt;code&gt;kedro run&lt;/code&gt; command used in the CLI. One changes is to make it more consistent. The flags &lt;code&gt;--node&lt;/code&gt;, &lt;code&gt;--tag&lt;/code&gt;, and &lt;code&gt;--load-version&lt;/code&gt; are deprecated in favour of plural equivalents (&lt;code&gt;--nodes&lt;/code&gt;, &lt;code&gt;--tags&lt;/code&gt;, and &lt;code&gt;--load-versions&lt;/code&gt;) and will be removed in Kedro 0.19.0. An additional change means that you can filter and run nodes by node namespace using the &lt;code&gt;--namespace&lt;/code&gt; flag with &lt;code&gt;kedro run&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is now support for using generator functions as nodes, i.e. using yield instead of return.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We added a new node argument to all four dataset hooks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find all the &lt;a href="https://github.com/kedro-org/kedro/releases/tag/0.18.5" rel="noreferrer"&gt;details about the Kedro version 0.18.5 release&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;h2 id="kedro-datasets-releases-"&gt;Kedro datasets releases &lt;/h2&gt;
&lt;p&gt;Kedro provides numerous different built-in datasets for various file types and file systems, to save you from having to write the logic for reading or writing data, including Pandas, Spark, Dask, NetworkX, Pickle, and more.&lt;/p&gt;
&lt;p&gt;There have been several datasets contributed by community members over the past months which include the addition of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;snowflake.SnowparkTableDataSet&lt;/code&gt; by &lt;a href="https://github.com/Vladimir-Filimonov" rel="noreferrer"&gt;Vladimir Filimonov&lt;/a&gt; and &lt;a href="https://github.com/heber-urdaneta" rel="noreferrer"&gt;Heber Urdaneta&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;polars.CSVDataSet&lt;/code&gt; by &lt;a href="https://github.com/wmoreiraa" rel="noreferrer"&gt;Walber Moreira&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we mentioned in “&lt;a href="https://medium.com/quantumblack/keeping-up-with-kedro-the-latest-developments-in-our-development-workflow-framework-cbcc415eea9c" rel="noreferrer"&gt;Keeping up with Kedro&lt;/a&gt;”, Kedro version 0.19.0 will move Kedro’s datasets from the main framework project into a separate package called Kedro-Datasets.&lt;/p&gt;
&lt;h2 id="kedro-viz-releases"&gt;Kedro-Viz releases&lt;/h2&gt;
&lt;p&gt;If you've not yet used it, Kedro-Viz is the interactive development tool for building data science pipelines with Kedro. It comes with an experiment tracking feature enabling you to view and compare different runs of your Kedro project. Check out the &lt;a href="http://demo.kedro.org/" rel="noreferrer"&gt;Kedro-Viz demo at demo.kedro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’ve made three releases of Kedro-Viz this year, plus a patch release. You can find further &lt;a href="https://github.com/kedro-org/kedro-viz/releases" rel="noreferrer"&gt;details of the Kedro-Viz releases on GitHub&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;To get the latest release of Kedro-Viz, you can use pip: &lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip install kedro-viz==6.0.0&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;or npm &lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm install @quantumblack/kedro-viz@latest&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here’s a summary of what we’ve been working on:&lt;/p&gt;
&lt;h3&gt;Kedro-Viz version 6.0.0&lt;/h3&gt;
&lt;p&gt;In this release we bumped the major version to 6.0.0 because of a change in the frontend React code (we bumped the minimum version of React from 16.8.6 to 17.0.2). Additional changes include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We added a change so you can now see a preview of your data in the metadata panel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can remove metrics plots from metadata panel and add links to the plots on experiment tracking. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can also link plot and JSON dataset names from experiment tracking to the flowchart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kedro-Viz no longer depends on pandas or Plotly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Kedro-Viz versions 5.3.0 and 5.2.0&lt;/h3&gt;
&lt;p&gt;We introduced a raft of updates to experiment tracking, the largest being the addition of time series &amp;amp; parallel coordinates metrics plots and delta values.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We’ve enabled the display of json objects with &lt;code&gt;react-json-viewer&lt;/code&gt; in experiment tracking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We added a feature to show/hide modular pipelines on the pipeline flowchart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s now possible to retrieve and share URL parameters for each element/section in the flowchart.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We've recently published a &lt;a href="https://kedro.org/blog/experiment-tracking-with-kedro" rel="noreferrer"&gt;blog post about experiment tracking&lt;/a&gt; to highlight the latest features and discuss what is coming next.&lt;/p&gt;
&lt;h2 id="whats-next-for-the-kedro-projects"&gt;What's next for the Kedro projects?&lt;/h2&gt;
&lt;p&gt;We have a broad range of &lt;a href="https://github.com/kedro-org/kedro/milestones" rel="noreferrer"&gt;milestones for the Kedro framework&lt;/a&gt; that cover areas such as integration with Databricks, enhancements for Jupyter Notebook users and ongoing changes such as the &lt;a href="https://medium.com/quantumblack/the-latest-kedro-developments-9a4d15a7ceb5" rel="noreferrer"&gt;transition of datasets into their own package&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the to-do list for Kedro-Viz, we’ve included enhanced navigation between flowchart and experiment tracking and collaboration features within Kedro-Viz.&lt;/p&gt;
&lt;p&gt;Stand by for a pair of virtual Kedro showcases on 5th April 2023 (&lt;a href="https://www.meetup.com/meetup-group-zltyafrj/events/292529526/" rel="noreferrer"&gt;9am BST&lt;/a&gt; and &lt;a href="https://www.meetup.com/meetup-group-zltyafrj/events/292533186/" rel="noreferrer"&gt;4pm BST&lt;/a&gt;) to demonstrate some of the features added in the recent releases to the global community. &lt;/p&gt;
&lt;p&gt;To suggest features to us, report bugs, or just see what we’re working on right now, visit the Kedro projects on &lt;a href="https://github.com/kedro-org" rel="noreferrer"&gt;GitHub&lt;/a&gt;. We welcome every contribution, large or small.&lt;/p&gt;

</description>
      <category>python</category>
      <category>kedro</category>
      <category>datascience</category>
      <category>news</category>
    </item>
    <item>
      <title>How to build a serverless WebSockets platform</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Tue, 06 Sep 2022 13:37:23 +0000</pubDate>
      <link>https://forem.com/ably/how-to-build-a-serverless-websockets-platform-5b5i</link>
      <guid>https://forem.com/ably/how-to-build-a-serverless-websockets-platform-5b5i</guid>
      <description>&lt;p&gt;&lt;a href="https://ably.com/blog/how-to-build-a-serverless-websocket-platform"&gt;First published on the Ably blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When building modern web applications, it is increasingly important to be able to handle realtime data with an event-driven architecture to propagate messages to all connected clients instantly.&lt;/p&gt;

&lt;p&gt;Several protocols are available, but WebSocket is arguably the most widely used as it is optimized for minimum overhead and low latency. The WebSocket protocol supports bidirectional, full-duplex communication between client and server over a persistent, single-socket connection. With a WebSocket connection, you can eliminate polling and push updates to a client as soon as an event occurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to integrate WebSockets into your stack
&lt;/h2&gt;

&lt;p&gt;You could deliver an event-driven architecture by setting up a dedicated WebSocket server for clients to connect and receive updates. However, this architecture has several drawbacks, including the need to manage and scale the server and the inherent latency involved in sending updates from that server to clients, who may be distributed worldwide.&lt;/p&gt;

&lt;p&gt;To scale up your hardware across the globe, you could skip the responsibility and cost of ownership by renting from a cloud service provider. For example, you could use a managed cloud service such as Amazon ECS, or you could opt for a serverless WebSocket solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“&lt;strong&gt;A serverless application is one that costs you nothing to run when nobody is using it, excluding data storage costs.&lt;/strong&gt;”&lt;/em&gt; &lt;a href="https://pauldjohnston.medium.com/cloud-2-0-code-is-no-longer-king-serverless-has-dethroned-it-c6dc955db9d5"&gt;Cloud 2.0: Code is no longer King — Serverless has dethroned it&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Saving on operational costs is an excellent reason to consider a serverless framework: you reap the benefits of scale and elasticity, but you don’t have to provision or manage the servers.&lt;/p&gt;

&lt;p&gt;This article explains how to build a basic serverless WebSockets platform, ideal for simple applications such as chat, using AWS API Gateway to create a WebSocket endpoint and AWS Lambda for connection management and backend business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic architecture of a serverless WebSockets platform
&lt;/h2&gt;

&lt;p&gt;Building a simple serverless WebSockets platform is relatively straightforward, using AWS API Gateway to build out WebSockets APIs and Lambda functions for backend processing, storing message metadata in Amazon DynamoDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS API Gateway
&lt;/h3&gt;

&lt;p&gt;AWS API Gateway is a service to create and manage APIs. You can use it as a stateful proxy to terminate persistent WebSocket connections and transfer data between your clients and HTTP-based backends such as AWS Lambda, Amazon Kinesis, or any other HTTP endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Lambda
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is a serverless compute service that allows you to run code without provisioning or managing servers.&lt;/p&gt;

&lt;p&gt;WebSocket APIs can invoke Lambda functions, for example, to deliver a message payload for further processing and then return the result to the client. The Lambda functions only run as long as necessary to handle any individual task. For client authorization, the connection request must include credentials to be verified by a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"&gt;Lambda authorizer function&lt;/a&gt; that returns the appropriate AWS IAM policy for access rights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: chat app powered by serverless WebSockets
&lt;/h2&gt;

&lt;p&gt;As is traditional, let’s look at an example chat app to illustrate how the serverless WebSockets solution powers a basic realtime application. First, consider the sequence by which a client WebSocket connection is made and stored.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A client device establishes a single WebSocket connection to AWS API Gateway. Each active WebSocket connection has an individual callback URL in API Gateway that is used to push messages back to the corresponding client.&lt;/li&gt;
&lt;li&gt; The connection message is sent to an API Gateway Authorizer Lambda function to confirm that the device has the correct credentials to connect to the service.&lt;/li&gt;
&lt;li&gt; The connection message and metadata are sent to a separate AWS Lambda when it has been authenticated.&lt;/li&gt;
&lt;li&gt; The Lambda handler inspects the metadata and stores appropriate information in AWS DynamoDB, a NoSQL document database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6h_Kz4FG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/09/basic-serverless-connection-with-aws%402x.png%3Ftr%3Dw-1520" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6h_Kz4FG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/09/basic-serverless-connection-with-aws%402x.png%3Ftr%3Dw-1520" alt="Diagram shows how a client establishes a WebSocket connection to API Gateway." width="880" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Client establishes a WebSocket connection to API Gateway.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a chat app, users can be involved in numerous conversations involving one or more participants. This is a typical use case for the publish and subscribe pattern. When a user sends a message to a particular conversation, or channel, the chat app &lt;em&gt;publishes&lt;/em&gt; the message to all other users who &lt;em&gt;subscribe&lt;/em&gt; to that channel. Each subscriber to that channel receives the message in their app.&lt;/p&gt;

&lt;p&gt;When the chat app initiates a WebSocket connection to AWS API Gateway, there needs to be a way to determine the channels for which that connection is authorized to publish and subscribe. The client code must send this information within the connection metadata.&lt;/p&gt;

&lt;p&gt;Now let’s consider the point at which the user sends a message, and another user receives it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A client sends a chat message, including the metadata to identify the particular channel on which to publish.&lt;/li&gt;
&lt;li&gt; On receipt, API Gateway sends it to the appropriate Lambda function.&lt;/li&gt;
&lt;li&gt; Upon invocation, the Lambda checks for the connections subscribed to that channel and sends API Gateway the ID for each subscriber and the message payload.&lt;/li&gt;
&lt;li&gt; API Gateway uses the callback URLs for each subscriber connection to send the payload to the client on the established WebSocket connection.&lt;/li&gt;
&lt;li&gt; Upon disconnection, a Lambda function is invoked to clean up the datastore.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mcwWjNEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/09/basic-serverless-message-send-with-aws%402x.png%3Ftr%3Dw-1520" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mcwWjNEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/09/basic-serverless-message-send-with-aws%402x.png%3Ftr%3Dw-1520" alt="Diagram shows how a message sent from one client is published in realtime to others on that channel" width="880" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A message sent from one client is published in realtime to others on that channel.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The limitations of basic serverless WebSockets
&lt;/h2&gt;

&lt;p&gt;The above is a simplistic example of a realtime, event-driven app using serverless WebSockets. Some of the limitations include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection state tracking doesn’t scale&lt;/strong&gt;: AWS API Gateway doesn’t track connection metadata, which is why it needs to be stored in a database such as &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html"&gt;Amazon DynamoDB&lt;/a&gt; using a Lambda function to update the store for every connection/disconnection. For large numbers of connecting clients, you’d hit Lambda scaling limits. You could instead use an AWS Integration to API Gateway to call DynamoDB directly, but you’ll need to be mindful of the associated high level of database usage.&lt;/p&gt;

&lt;p&gt;Another aspect that we’ve not addressed is that of abrupt disconnection. In cases where a client connection drops without warning, the active WebSocket connection isn’t properly cleaned up. The database will potentially store connection identifiers that are no longer present, which can cause inefficiencies.&lt;/p&gt;

&lt;p&gt;One way to avoid zombie connections is to periodically check if a connection is “alive” by using a heartbeat mechanism such as TCP keepalives or &lt;a href="https://tools.ietf.org/html/rfc6455#page-36"&gt;ping/pong control frames&lt;/a&gt;. Sending heartbeats negatively impacts the system’s scalability and dependability, especially when handling millions of concurrent WebSocket connections, since it creates an extra load on the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You cannot broadcast messages to all connected clients&lt;/strong&gt;: In this simple design, there is no way to simultaneously send a message to multiple connections, as you’d expect from a standard pub/sub channel or topic. To fan out an update by publishing it to thousands of clients would require you to fetch the connection IDs from the database and then make an API call to each, which isn’t a scalable approach. You could alternatively consider Amazon SNS to add pub/sub to your app, but there are still limitations and the downside of additional complexity.&lt;/p&gt;

&lt;p&gt;For a chat application example, you can assume that typical channels will have a low number of subscribers unless they’re used in interactive livestream sessions with thousands of users. Other scenarios that can tolerate a lack of broadcast include individual update notifications and interactivity features that work on an item-by-item basis. Live data fanout use cases cannot support this limitation if there are a large number of subscribers on a channel to receive the latest sports score or news update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is a limit on the number of WebSocket connections allowed per second&lt;/strong&gt;: AWS API Gateway sets a per region limit of &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html"&gt;500 new connections per second per account&lt;/a&gt; and 10,000 simultaneous connections. This may be insufficient if you plan to scale your application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution is bound to a single AWS region&lt;/strong&gt;: The WebSocket connections offered by AWS API Gateway are bound to a single region, resulting in poor performance because of latency for clients geographically distant from that region. It’s possible to use &lt;a href="https://ably.zoom.us/j/87643486944?pwd=ZEh1MzgxQUFOKytQSEFrbUNyS2UxUT09"&gt;Amazon EventBridge&lt;/a&gt;, a serverless event bus, for cross-region event routing to replicate events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges of building a production-ready serverless WebSockets platform
&lt;/h2&gt;

&lt;p&gt;The architecture above has potential if used to build out a simple realtime messaging platform, although we’ve already seen that it has limitations at scale. If you need a production-ready system, you will require scale but also need to consider the dependability of your solution in terms of performance, the integrity of message delivery, fault tolerance, and fallback support, all of which can be complex and time-consuming to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;In a distributed system, latency deteriorates the further it travels, so data should be kept as close to the users as possible via managed data centers and &lt;a href="https://ably.com/blog/what-is-edge-messaging"&gt;edge acceleration points&lt;/a&gt;. It isn’t sufficient to minimize latency, however. The user experience needs latency variance to be at a minimum to ensure predictability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Message integrity
&lt;/h3&gt;

&lt;p&gt;When a user’s connection drops and reconnects, an app needs to pick up with minimal friction from the point before they disconnected. Any missed messages need to be delivered without duplicating those already processed. The whole experience needs to be &lt;a href="https://ably.com/blog/achieving-exactly-once-message-processing-with-ably"&gt;completely seamless&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fault tolerance &amp;amp; scalability
&lt;/h3&gt;

&lt;p&gt;The demand for a service can be unpredictable, and it is challenging to plan and provision sufficient capacity. A realtime solution must be highly available at all times to support surges in data. The challenge is to scale horizontally and swiftly absorb millions of connections without the need for pre-provisioning.&lt;/p&gt;

&lt;p&gt;Your solution must also be &lt;a href="https://ably.com/blog/engineering-dependability-and-fault-tolerance-in-a-distributed-system"&gt;fault-tolerant&lt;/a&gt; to continue operating even if a component fails. There need to be multiple components capable of maintaining the system if some are lost. There should be no single point of congestion and no single point of failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fallback transports
&lt;/h3&gt;

&lt;p&gt;Despite widespread platform support, the WebSocket protocol is not supported by all proxies or browsers, and some corporate firewalls even block specific ports, which can affect WebSocket access. You may consider supporting fallback transports, such as XHR streaming, XHR polling, or long polling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature creep
&lt;/h3&gt;

&lt;p&gt;Another challenge to using raw serverless WebSockets in a production-ready system may only present itself after successfully using it in the product’s first iteration. While you’ve added the essential capability to receive realtime updates, feature creep often means that the features seed additional requirements for shared live experiences and collaborative features.&lt;/p&gt;

&lt;p&gt;Building and maintaining a proprietary WebSocket solution to support the future realtime needs of a product can be challenging. The infrastructure that underpins the system must be stable and dependable and requires experienced engineers to build and maintain it. A development team may find they have a long-term commitment to supporting the realtime solution rather than focussing on the features that augment the core product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ably: A serverless WebSocket PaaS built for scale
&lt;/h2&gt;

&lt;p&gt;Adopting a serverless WebSocket solution from a third-party vendor makes more financial sense for many organizations. Otherwise, you could spend several months and a heap of money. And you’d end up burdened with the high cost of infrastructure ownership, technical debt, and ongoing demands for engineering investment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ably.com"&gt;Ably&lt;/a&gt; has fault-tolerant, highly-available, elastic global infrastructure for effortless scaling and low complexity. Our serverless WebSockets platform is mathematically modeled around &lt;a href="https://ably.com/four-pillars-of-dependability"&gt;the Four Pillars of Dependability&lt;/a&gt;. We can ensure messages are delivered at low latency over a secure, reliable, and global edge network. There’s no DevOps overhead, and there’s no infrastructure to manage.&lt;/p&gt;

&lt;p&gt;The Ably platform abstracts the worry of building a realtime solution so that you can prioritize your product’s roadmap and develop features to stay competitive.&lt;/p&gt;

&lt;p&gt;While Ably’s native protocol is WebSocket, there is rarely a one-size-fits-all protocol: different protocols serve different purposes better than others. Ably offers multiple &lt;a href="https://ably.com/protocols"&gt;protocols&lt;/a&gt; such as WebSocket, MQTT, SSE, and raw HTTP.&lt;/p&gt;

&lt;p&gt;To support live experiences, Ably adds features such as &lt;a href="https://ably.com/docs/core-features/presence"&gt;device presence&lt;/a&gt;, &lt;a href="https://ably.com/docs/realtime/history"&gt;stream history&lt;/a&gt;, &lt;a href="https://ably.com/docs/realtime/channels/channel-parameters/rewind"&gt;channel rewind&lt;/a&gt;, and handling for &lt;a href="https://ably.com/docs/realtime/connection#connection-state-recovery"&gt;abrupt disconnections&lt;/a&gt;, making building rich realtime applications easier. There is a range of webhook integrations for triggering business logic in realtime. We offer a gateway to serverless functions from your preferred cloud service providers. You can deploy best-in-class tooling across your entire stack and build event-driven apps using the ecosystems you’re already invested in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CzTPMh4n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/09/how-ably-augments-basic-serverless-websockets%402x.png%3Ftr%3Dw-1520" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CzTPMh4n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/09/how-ably-augments-basic-serverless-websockets%402x.png%3Ftr%3Dw-1520" alt="Ably extends a basic serverless WebSockets solution to augment your product" width="880" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ably extends a basic serverless WebSockets solution to augment your product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ably enables you to deliver a live experience without delays in going to market, runaway costs, or unhappy users. Using Ably allows your engineering teams to focus on core product innovation without having to provision and maintain realtime infrastructure. &lt;a href="https://ably.com/case-studies"&gt;Our existing customers&lt;/a&gt; already build massively scalable realtime apps with us, with rapid go-to-market and average savings of $500k on build cost.&lt;/p&gt;

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

&lt;p&gt;In this article, we have reviewed the architecture of a simple serverless WebSockets platform using Amazon API Gateway and AWS Lambda. We have also considered some of the limitations of this design, such as lack of support for broadcasting, single-region deployment, and connection management.&lt;/p&gt;

&lt;p&gt;We introduced Ably’s serverless WebSocket platform, which reliably handles high-scale realtime data distribution to web and mobile apps at the edge.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a realtime chat app with Laravel using WebSockets</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Mon, 18 Jul 2022 09:50:37 +0000</pubDate>
      <link>https://forem.com/ably/building-a-realtime-chat-app-with-react-laravel-and-websockets-580d</link>
      <guid>https://forem.com/ably/building-a-realtime-chat-app-with-react-laravel-and-websockets-580d</guid>
      <description>&lt;p&gt;You use realtime communication every day. It is the simultaneous exchange of information between a sender and a receiver with almost zero latency. Internet, landlines, mobile/cell phones, instant messaging (IM), internet relay chat, videoconferencing, teleconferencing, and robotic telepresence are all examples of realtime communication systems.&lt;/p&gt;

&lt;p&gt;In this tutorial, you’ll learn how to build a web based &lt;a href="https://hubs.la/Q01JtTqm0" rel="noopener noreferrer"&gt;realtime chat app&lt;/a&gt; using &lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt;, &lt;a href="https://hubs.la/Q01JtTrm0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; and &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;Vue.js&lt;/a&gt;. You’ll use Vue.js to build the frontend/UI and Laravel as a backend to interact with Ably Realtime APIs to facilitate realtime communication. By building this type of application, you’ll learn about the core concepts for building realtime apps using the Laravel-Ably integration.&lt;/p&gt;

&lt;p&gt;Please note that you can use any frontend framework (&lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;react&lt;/a&gt;, &lt;a href="https://angular.io/" rel="noopener noreferrer"&gt;angular&lt;/a&gt;, &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;next.js&lt;/a&gt; etc.) to build the Laravel chat app and there is no dependency on any specific framework.&lt;/p&gt;

&lt;p&gt;The source code for the project is available &lt;a href="https://github.com/ably-labs/laravel-broadcast-app" rel="noopener noreferrer"&gt;in this GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture of the realtime chat app
&lt;/h2&gt;

&lt;p&gt;Before getting started, you should familiarize yourself with the tech stack that you’ll be using in this tutorial to build your chat application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; is a PHP-based web application framework with expressive, elegant syntax. The web development community loves Laravel because it provides an amazing developer experience and supports advanced concepts, like queues, dependency injection, unit testing, and integration testing. Laravel is a great choice for building realtime apps, and it already comes with support for Ably to "broadcast" your server-side Laravel events over a WebSocket connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ably
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q01JtTrm0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; is a &lt;a href="https://hubs.la/Q01JtTqK0" rel="noopener noreferrer"&gt;pub/sub messaging platform&lt;/a&gt; that powers synchronized digital experiences in realtime for millions of simultaneously connected devices around the world. Ably offers WebSockets, stream resume, history, presence, and managed third-party integrations to make it simple to build, extend, and deliver digital realtime experiences at scale. Ably makes use of different transports to make sure data is delivered in realtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue.js
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;Vue&lt;/a&gt; is a frontend javaScript framework for building user interfaces. It builds on top of standard HTML, CSS, and JavaScript and provides a declarative and component-based programming model that helps you efficiently develop user interfaces, be they simple or complex.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel broadcasting
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://laravel.com/docs/broadcasting" rel="noopener noreferrer"&gt;Laravel broadcasting&lt;/a&gt; is a feature provided by Laravel as a way for a client (frontend) to publish and receive information in realtime via channels. A channel is a logical communication medium that helps to publish and subscribe to information over a WebSocket connection. There are 3 types of channels.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Public channels
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The client doesn't need to be authenticated (logged in) to use public channels.&lt;/li&gt;
&lt;li&gt;The client can only subscribe to the data on this channel. To publish data, it needs to be done via the laravel-server (backend) i.e. &lt;code&gt;client1 =&amp;gt; laravel =&amp;gt; ably =&amp;gt; client 2, client 3, client 4...&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Private channels
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The client needs to be authenticated to use private channels.&lt;/li&gt;
&lt;li&gt;The client is directly able to publish and subscribe to data through Ably using &lt;a href="https://laravel.com/docs/broadcasting#client-events" rel="noopener noreferrer"&gt;client-events&lt;/a&gt;. i.e. &lt;code&gt;client1 =&amp;gt; ably =&amp;gt; client 2, client 3, client 4...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The client can also &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/routes/api.php" rel="noopener noreferrer"&gt;publish data through laravel-backend&lt;/a&gt;, as done for public channels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Presence channels
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;These are similar to private channels, offering all of the features of private channels, as well as a way to register client presence on the channel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In this blog we will only look at public channels.&lt;/strong&gt; So, the architecture of the realtime chat app will look 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjz1ctw9bs0jue0w4tevo.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%2Fjz1ctw9bs0jue0w4tevo.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chat app uses &lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;laravel&lt;/a&gt; + &lt;a href="http://github.com/ably/laravel-broadcaster" rel="noopener noreferrer"&gt;ably-broadcaster&lt;/a&gt; and &lt;a href="https://hubs.la/Q01JtTrm0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; at server side (backend) and &lt;a href="https://vuejs.org/guide/introduction.html#what-is-vue" rel="noopener noreferrer"&gt;vue.js&lt;/a&gt; + &lt;a href="https://github.com/ably-forks/laravel-echo" rel="noopener noreferrer"&gt;ably-laravel-echo&lt;/a&gt; at client side (frontend). &lt;/p&gt;

&lt;p&gt;Laravel-server is used for authenticating/authorizing clients and broadcasting information to ably on behalf of clients over a given channel. In the current example, since public channels don’t need any sort of authentication, it will only be used to broadcast information.&lt;/p&gt;

&lt;p&gt;Clients (guest-users) use HTTP connection to &lt;strong&gt;post a message with a channel-name&lt;/strong&gt; to Laravel-server. Laravel-server publishes the same message to Ably (on the same channel) using a named event. Ably is responsible for sending this information to clients (subscribed to the same channel) using the provided event over a WebSocket connection. Guest users consume this event on the client-side using the &lt;a href="https://github.com/ably-forks/laravel-echo" rel="noopener noreferrer"&gt;Laravel Echo&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;Publishing a message on a channel is done using an event. &lt;strong&gt;Events are the primary method of packaging a message on the channel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simpler terms&lt;/strong&gt;, when a message is published on a channel with an eventName, it will be received by a subscriber matching the same channelName and eventName.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the environment
&lt;/h2&gt;

&lt;p&gt;Here is what we will need to get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.php.net/manual/en/install.php" rel="noopener noreferrer"&gt;PHP&lt;/a&gt; version &amp;gt;= 7.3&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; version &amp;gt;=14.x.x&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up the basic project structure
&lt;/h3&gt;

&lt;p&gt;Execute the following command in the terminal to create a Laravel project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer create-project laravel/laravel ably-laravel-chat-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following commands to get the auto-generated app running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ably-laravel-chat-app
composer install
npm install
php artisan key:generate
php artisan serve // run in a separate terminal
npm run watch // run in a separate terminal 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, open &lt;a href="http://localhost:8000/" rel="noopener noreferrer"&gt;localhost:8000&lt;/a&gt; in your browser, and you’ll see your Laravel project running.&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%2Fik.imagekit.io%2Fably%2Fghost%2Fprod%2F2022%2F04%2Flaravel-app-home-page.png%3Ftr%3Dw-1520" 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%2Fik.imagekit.io%2Fably%2Fghost%2Fprod%2F2022%2F04%2Flaravel-app-home-page.png%3Ftr%3Dw-1520" alt="Laravel app home page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up your Ably account
&lt;/h2&gt;

&lt;p&gt;To start, you need to &lt;a href="https://hubs.la/Q01JtTwK0" rel="noopener noreferrer"&gt;create an Ably account&lt;/a&gt; if you don’t have one. Next, you need to grab the API key from the Ably dashboard:&lt;/p&gt;

&lt;p&gt;1. Visit your &lt;a href="https://hubs.la/Q01JtTBR0" rel="noopener noreferrer"&gt;app dashboard&lt;/a&gt; and click &lt;strong&gt;Create New App&lt;/strong&gt; with name &lt;strong&gt;Chat-App&lt;/strong&gt;.&lt;br&gt;
2. Once the app has been created, go to the &lt;strong&gt;API Keys&lt;/strong&gt; tab. Copy the first key with the name Root. Keep the key safe. The key shouldn't be exposed to the client code. &lt;/p&gt;

&lt;p&gt;That’s it. Your Ably account is ready to support realtime messaging.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Ably broadcaster on Laravel
&lt;/h2&gt;

&lt;p&gt;Please follow the &lt;a href="https://github.com/ably/laravel-broadcaster#setup" rel="noopener noreferrer"&gt;Setup section&lt;/a&gt; to configure the Ably broadcaster.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create PublicMessageEvent Class
&lt;/h3&gt;

&lt;p&gt;To send events from Laravel to Ably, create an event file by running the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:event PublicMessageEvent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create a &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/app/Events/PublicMessageEvent.php" rel="noopener noreferrer"&gt;PublicMessageEvent.php&lt;/a&gt; file in the &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/tree/main/app/Events" rel="noopener noreferrer"&gt;app/Events&lt;/a&gt; directory. Replace the content of the files with following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PublicMessageEvent implements ShouldBroadcast
{
    public $channelName;
    public $message;

    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($channelName, $message)
    {
        $this-&amp;gt;channelName = $channelName;
        $this-&amp;gt;message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     * https://laravel.com/docs/broadcasting#model-broadcasting-channel-conventions
     * 
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return [new Channel($this-&amp;gt;channelName)];
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above specifies:&lt;/p&gt;

&lt;p&gt;1. The PublicMessageEvent class that implements the &lt;strong&gt;ShouldBroadcast&lt;/strong&gt; interface.&lt;br&gt;
2. By default the class name is used as an event name. This means eventName will be &lt;strong&gt;PublicMessageEvent&lt;/strong&gt;. You can change eventName using &lt;a href="https://laravel.com/docs/9.x/broadcasting#broadcast-name" rel="noopener noreferrer"&gt;broadcastAs&lt;/a&gt;.&lt;br&gt;
3. The &lt;a href="https://laravel.com/docs/9.x/broadcasting#model-broadcasting-channel-conventions" rel="noopener noreferrer"&gt;broadcastOn&lt;/a&gt; method that is responsible for returning the channel/list of channels on which the event should broadcasted.&lt;/p&gt;
&lt;h3&gt;
  
  
  Broadcasting using PublicMessageEvent class
&lt;/h3&gt;

&lt;p&gt;To handle API requests from frontend, open the &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/routes/api.php" rel="noopener noreferrer"&gt;routes/api.php&lt;/a&gt; file and add following route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::post('/public-event', function (Request $request) {
    $channelName = $request-&amp;gt;post('channelName');
    $message = $request-&amp;gt;post('message');
    broadcast(new PublicMessageEvent( $channelName, $message ));
})-&amp;gt;middleware('throttle:60,1'); // 60 requests/minute are allowed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above specifies:&lt;/p&gt;

&lt;p&gt;1. A new API route to handle POST request from client. This API route will be available at &lt;strong&gt;localhost:8000/api/public-event&lt;/strong&gt;.&lt;br&gt;
2. When called, the message is broadcasted to ably on the given channel with &lt;strong&gt;eventName as PublicMessageEvent&lt;/strong&gt;.&lt;br&gt;
3. Throttle to 60 requests per minute for each IP address. It will return an error for requests exceeding the given limit. This is mainly to avoid spamming on public channels.&lt;/p&gt;

&lt;p&gt;To check if the code is working correctly so far:&lt;br&gt;
1. Restart the server using &lt;code&gt;php artisan serve&lt;/code&gt;. &lt;br&gt;
2. Attach to channel &lt;code&gt;public:community&lt;/code&gt; for new messages using the &lt;a href="https://hubs.la/Q01JtTD80" rel="noopener noreferrer"&gt;Ably Dev Console&lt;/a&gt;.&lt;br&gt;
3. Try sending a &lt;strong&gt;POST&lt;/strong&gt; request to the &lt;strong&gt;localhost:8000/api/public-event&lt;/strong&gt; endpoint using &lt;a href="https://curl.se/docs/manual.html" rel="noopener noreferrer"&gt;curl&lt;/a&gt;, &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; or any other tool with &lt;strong&gt;channelName and message&lt;/strong&gt; as POST method body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl --location --request POST 'localhost:8000/api/public-event' \
--header 'Content-Type: application/json' \
--data-raw '{
    "channelName":"public:community",
    "message":"Hi there"
}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Check for this message in the &lt;a href="https://hubs.la/Q01JtTD80" rel="noopener noreferrer"&gt;Ably Dev Console&lt;/a&gt; under the attached channel tab.&lt;/p&gt;

&lt;p&gt;Next, we will look at setting up frontend to send and receive messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Laravel Frontend
&lt;/h2&gt;

&lt;p&gt;Following packages need to be installed for proper communication with Laravel and Ably.&lt;/p&gt;

&lt;p&gt;1. &lt;a href="https://github.com/ably-forks/echo/" rel="noopener noreferrer"&gt;Laravel Echo&lt;/a&gt;: This JavaScript library allows clients/users to subscribe to channels and listen to the events sent from ably.&lt;br&gt;
2. &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;Axios&lt;/a&gt;: This package helps to make HTTP requests from client to laravel-server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @ably/laravel-echo ably axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Initialize Laravel-Echo
&lt;/h3&gt;

&lt;p&gt;In the &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/resources/js/bootstrap.js" rel="noopener noreferrer"&gt;resources/js/bootstrap.js&lt;/a&gt; file, add the following snippet as per the &lt;a href="https://github.com/ably-forks/laravel-echo#installation" rel="noopener noreferrer"&gt;Echo Setup Section&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Echo from '@ably/laravel-echo';

window.Ably = require('ably');

// Create new echo client instance using ably-js client driver.
window.Echo = new Echo({
    broadcaster: 'ably',
});

// Register a callback for listing to connection state change 
window.Echo.connector.ably.connection.on((stateChange) =&amp;gt; {
    console.log("LOGGER:: Connection event :: ", stateChange);
    if (stateChange.current === 'disconnected' &amp;amp;&amp;amp; stateChange.reason?.code === 40142) { // key/token status expired
        console.log("LOGGER:: Connection token expired https://help.ably.io/error/40142");
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building the front-end UI
&lt;/h2&gt;

&lt;p&gt;Please take a look at &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/resources/js/app.js" rel="noopener noreferrer"&gt;app.js&lt;/a&gt;, &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/resources/js/components/ChatComponent.vue" rel="noopener noreferrer"&gt;ChatComponent.vue&lt;/a&gt; and &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/resources/js/components/MessageComponent.vue" rel="noopener noreferrer"&gt;MessageComponent.vue&lt;/a&gt; from the existing &lt;a href="https://github.com/ably-labs/laravel-broadcast-app" rel="noopener noreferrer"&gt;repository&lt;/a&gt; under &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/tree/main/resources/js/components" rel="noopener noreferrer"&gt;resources/js/&lt;/a&gt; for building the UI for your chat app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending messages
&lt;/h3&gt;

&lt;p&gt;In the &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/resources/js/components/ChatComponent.vue" rel="noopener noreferrer"&gt;ChatComponent.vue&lt;/a&gt; file, you can see the &lt;code&gt;broadcastMessageOnPublicChannel&lt;/code&gt; method is used for broadcasting data on a given public channel using axios.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;broadcastMessageOnPublicChannel() {
    const message = this.message?.trim();
    if(!message)
        return;
    const publicChannelName = this.getActiveChannel().name;
    const broadcastUrl = window.location.origin + "/api/public-event";
    axios.post(broadcastUrl, { channelName : publicChannelName, message });
    this.message = null;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above specifies:&lt;/p&gt;

&lt;p&gt;1. &lt;strong&gt;publicChannelName&lt;/strong&gt; is generally prefixed with &lt;code&gt;public:&lt;/code&gt;.&lt;br&gt;
2. When &lt;strong&gt;broadcastMessageOnPublicChannel&lt;/strong&gt; is called, it sends a message from the client to laravel-server at endpoint &lt;strong&gt;/api/public-event&lt;/strong&gt; using axios.&lt;br&gt;
3. Axios sends a HTTP POST request with &lt;strong&gt;channelName&lt;/strong&gt; and &lt;strong&gt;message&lt;/strong&gt; as a request body.&lt;/p&gt;
&lt;h3&gt;
  
  
  Receiving messages
&lt;/h3&gt;

&lt;p&gt;In the &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/blob/main/resources/js/components/ChatComponent.vue" rel="noopener noreferrer"&gt;ChatComponent.vue&lt;/a&gt; file, the &lt;strong&gt;listen&lt;/strong&gt; method describes handler for &lt;strong&gt;PublicMessageEvent&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Echo.channel(channelName)
.subscribed(() =&amp;gt; // Do stuff when channel is attached )
.listenToAll(() =&amp;gt; // Do stuff when any event is received )
.listen('PublicMessageEvent', (data) =&amp;gt; {
    const channel = this.getChannelByName(channelName, 'public');
    this.updateBroadcastNotificationOnUI(channel, data)
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above specifies:&lt;/p&gt;

&lt;p&gt;1. Public channel is created using &lt;strong&gt;Echo.Channel(channelName)&lt;/strong&gt;.&lt;br&gt;
2. Actual publicChannelName is accessed using &lt;strong&gt;Echo.Channel(channelName).name&lt;/strong&gt; and is prefixed with &lt;code&gt;public:&lt;/code&gt;. i.e. If channelName is provided as &lt;strong&gt;community&lt;/strong&gt;, internally it uses channel name as &lt;strong&gt;public:community&lt;/strong&gt; while communicating with ably.&lt;br&gt;
3. The method &lt;code&gt;listenToAll&lt;/code&gt; registers a handler which gets called for any event received.&lt;br&gt;
4. The method &lt;code&gt;listen&lt;/code&gt; registers a handler for a specific &lt;strong&gt;PublicMessageEvent&lt;/strong&gt; and updates the received message on the UI once called.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the chat app
&lt;/h2&gt;

&lt;p&gt;To test your chat app, open the &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;localhost:3000&lt;/a&gt; in at least two browser tabs. Join the public room in each tab and broadcast messages to each other. For more information, refer to Guest Mode under the &lt;a href="https://github.com/ably-labs/laravel-broadcast-app/#usage" rel="noopener noreferrer"&gt;Usage Section&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%2Fuploads%2Farticles%2F4jvgrd242bdu9t50vynk.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%2F4jvgrd242bdu9t50vynk.png" alt="How to test your chat app"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;That’s it! In this tutorial, you learned how to create a realtime chat app using Laravel and Ably for guest users. In a future blog, we will cover how to add chat functionality for authenticated (logged in) users. Depending on your requirements, you can also discover how to add more features to your chat app in the &lt;a href="https://hubs.la/Q01JtTJg0" rel="noopener noreferrer"&gt;Ably chat reference guide&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We would love to hear about how you plan to use Ably in your applications. Join our &lt;a href="http://go.ably.com/discord" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt; and post your ideas for builds in the showcase channel to share, we’re currently giving out Ably tees to members who participate!&lt;/p&gt;

</description>
      <category>laravel</category>
    </item>
    <item>
      <title>Engage your site visitors with live updates using Ably, Next.js, Prisma, and PlanetScale</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Tue, 28 Jun 2022 11:54:46 +0000</pubDate>
      <link>https://forem.com/ably/engage-your-site-visitors-with-live-updates-using-ably-nextjs-prisma-and-planetscale-36f6</link>
      <guid>https://forem.com/ably/engage-your-site-visitors-with-live-updates-using-ably-nextjs-prisma-and-planetscale-36f6</guid>
      <description>&lt;p&gt;Not so long ago, email was considered revolutionary. Being able to send a message to someone on the other side of the world and receive a reply in a matter of minutes was a game-changer.&lt;/p&gt;

&lt;p&gt;Today, however, email is the slow-moving dinosaur of the Internet. Now, users expect instantaneous feedback from their online interactions and to communicate with people and machines instantly. If your app requires them to constantly refresh a web page to see the latest updates, they’ll soon be looking elsewhere. &lt;/p&gt;

&lt;p&gt;But how has technology kept pace with these increasing demands?&lt;/p&gt;

&lt;p&gt;One approach is the &lt;a href="https://ably.com/topic/pub-sub"&gt;publish/subscribe&lt;/a&gt; architectural pattern (pub/sub for short). It’s a method of asynchronous communication that enables applications to publish messages to a channel, and any clients subscribed to that channel to receive them.&lt;/p&gt;

&lt;p&gt;This tutorial will show you how to use pub/sub to implement live commenting in a blog. As soon as someone adds a comment to a blog article, anyone reading that article will see it immediately: no page refresh required. &lt;a href="https://ably-next-prisma-planetscale.vercel.app/"&gt;See it in action!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ability to comment immediately on a blog article might not be something you’ve missed in the past, but it’s a simple, self-contained example of an application that uses a live experience to engage its audience. You can build upon what you learn here to implement other audience engagement features to add to your own applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech stack
&lt;/h2&gt;

&lt;p&gt;To achieve this, we’re going to be using the following technologies:&lt;/p&gt;

&lt;h3&gt;
  
  
  Next.js
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; is a React framework that makes it easy to build fast and scalable web applications. React is an incredibly popular and powerful UI library, but requires extensive configuration. Next.js eliminates much of that work, and also provides developers with options on whether to render content on the client, server, or using a mixture of both.&lt;/p&gt;

&lt;p&gt;It would be handy if you knew a little bit about Next.js before you start this tutorial, but it’s not strictly necessary because we’ll provide a good starting point for building your application and guide you through the rest. But the Next.js website has a great &lt;a href="https://nextjs.org/learn/foundations/about-nextjs"&gt;mini-course&lt;/a&gt; that will get you up to speed quickly and their &lt;a href="https://nextjs.org/docs/getting-started"&gt;docs&lt;/a&gt; are awesome.&lt;/p&gt;

&lt;h3&gt;
  
  
  PlanetScale
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://planetscale.com/"&gt;PlanetScale&lt;/a&gt; is a database-as-a-service platform. It allows developers to quickly create highly-scalable databases and work with those databases as if they were code in a Git repository. Developers can create branches for development and push those to production when they are working as intended. You’ll be using PlanetScale to store blog comments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prisma
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.prisma.io/"&gt;Prisma&lt;/a&gt; is a Node.js and TypeScript ORM. It is a server library which helps developers write queries to work with databases using a familiar object-oriented approach. You will be using it to work with your PlanetScale database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ably
&lt;/h3&gt;

&lt;p&gt;Ably is a feature-rich pub/sub messaging platform that empowers developers to create engaging digital experiences in realtime. It guarantees message delivery to all manner of devices at huge scales while preserving the order and integrity of messages. You will use it to publish blog comments to a channel and to subscribe to that channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vercel
&lt;/h3&gt;

&lt;p&gt;Finally, you’ll be using &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; to host your application and make it available to the world. The nice folks at Vercel are the same people who brought you Next.js, making deployment of your application a breeze.&lt;/p&gt;

&lt;p&gt;Now that we know what we’re going to build and what we’re going to build it with, let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Next.js application
&lt;/h3&gt;

&lt;p&gt;There’s a lot of boilerplate code in this application, which we’ve created for you so that you can focus on incorporating the realtime elements. Start off by forking the Github repository for this tutorial.&lt;/p&gt;

&lt;p&gt;clicking on the Fork button on the repository’s home page. A forked copy of that Git repository will be added to your personal GitHub or GitLab repo. That’s it.&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 https://github.com/ably/ably-next-prisma-planetscale.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This repo contain two branches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt; - which contains the solution code for this tutorial&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;starter-project&lt;/code&gt; - which is the starting point for this tutorial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the &lt;code&gt;starter-project&lt;/code&gt; branch: you’ll be working with this branch for the whole of this tutorial. Use the &lt;code&gt;main&lt;/code&gt; branch to compare your code with the solution code if you get stuck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout starter-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The starter project gives you all the base components necessary to start implementing realtime communication and data persistence. It was built using &lt;a href="https://mui.com/"&gt;MUI&lt;/a&gt;, previously known as Material-UI.&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;code&gt;components&lt;/code&gt; folder and get a feel for which component does what.&lt;/p&gt;

&lt;p&gt;Execute the following &lt;code&gt;npm&lt;/code&gt; commands at your project’s root directory  to install the core dependencies and launch the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; in your browser and you’ll see your sample blog app, with some dummy content and a section for commenting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6CiWvMGq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic1.png%3Ftr%3Dw-800" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6CiWvMGq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic1.png%3Ftr%3Dw-800" alt="Starter project: a simple article with comments" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next.js will continue to hot-reload the project as you develop, so as a general rule you can see it taking shape as you work through the remaining steps without having to restart the server. When you update certain configuration files you &lt;em&gt;will&lt;/em&gt; have to restart the server, but we’ll let you know when that’s the case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Install the Ably React Hooks package
&lt;/h3&gt;

&lt;p&gt;The realtime commenting that you will implement in this app is provided by Ably.&lt;/p&gt;

&lt;p&gt;Next.js is powered by React and React can be a bit fussy about where and when you use code from libraries such as Ably’s. To make life simpler for React developers, Ably has developed the &lt;a href="https://www.npmjs.com/package/@ably-labs/react-hooks"&gt;Ably React Hooks&lt;/a&gt; NPM module. Install it into your project now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ably @ably-labs/react-hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Register for Ably
&lt;/h3&gt;

&lt;p&gt;To use Ably’s service, you need to &lt;a href="https://ably.com/signup"&gt;sign up for a free account&lt;/a&gt; and obtain an API key.&lt;/p&gt;

&lt;p&gt;Your new Ably account comes with a pre-created project called &lt;strong&gt;Default&lt;/strong&gt;, and your API key for that project will be displayed on your screen.&lt;/p&gt;

&lt;p&gt;If, for any reason, you don’t see it, click the &lt;strong&gt;Default&lt;/strong&gt; project in the dashboard and then select the API Keys tab at the top. Click the &lt;strong&gt;Show&lt;/strong&gt; button to display the root API key (not to be confused with the Subscribe only API key, which will only let you read messages from a channel, and not publish to it). So make sure you get the right one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2OsVWQfy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic2.png%3Ftr%3Dw-800" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2OsVWQfy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic2.png%3Ftr%3Dw-800" alt="Ably UI displaying the root API key" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure Ably React Hooks
&lt;/h3&gt;

&lt;p&gt;In this step you will instantiate the Ably client and authenticate with the Ably platform.&lt;/p&gt;

&lt;p&gt;First, create a file called &lt;code&gt;.env&lt;/code&gt; in your project’s root directory and create a configuration key called &lt;code&gt;ABLY_API_KEY&lt;/code&gt; which stores the API key you retrieved in the preceding step. Note that, even though this API key is an alphanumeric string, you don’t need to surround it with quotes.&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="nv"&gt;ABLY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;&lt;span class="nb"&gt;paste &lt;/span&gt;your api key here&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the many great things about Next.js is that it supports API routes. These enable you to build API endpoints as Node.js serverless functions. This is really useful for us, because we can create an API endpoint that authenticates with the Ably service without having to expose our API key to the browser.&lt;/p&gt;

&lt;p&gt;Implement an API endpoint called &lt;code&gt;/createTokenRequest&lt;/code&gt; which will generate a token for you to authenticate with Ably on the client-side. Next.js makes it really easy to include API routes in your application. All you need to do is create the route handler in the &lt;code&gt;pages/api&lt;/code&gt; directory, in a file of the same name.&lt;/p&gt;

&lt;p&gt;In your Next.js application, create a &lt;code&gt;createTokenRequest.js&lt;/code&gt; file in your &lt;code&gt;pages/api&lt;/code&gt; folder. This route is then available as the &lt;code&gt;/createTokenRequest&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Inside that file, create a new Ably client and use it to generate a new token:&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="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/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="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="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;tokenRequestData&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;client&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="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;ably-blog-app&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenRequestData&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;You can then use this endpoint to configure Ably on the client-side. But, if you were to do so using a relative, rather than absolute URL, this would generate an error due to a bug in one of its dependencies. It wouldn’t stop the Ably service from working, but it would soon clutter up your console. So let’s fix that now by creating an environment variable for the URL. This URL will change when we deploy to Vercel in a later step.&lt;/p&gt;

&lt;p&gt;It also needs to be available to the browser, which it isn’t by default. Luckily, Next.js can help with that. Any environment variables prefixed by &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; are accessible on the client. So don’t ever put anything sensitive in there (such as your API key), but it’s a good place to configure your application’s host URL. Enter the following in your &lt;code&gt;.env&lt;/code&gt; file:&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="nv"&gt;NEXT_PUBLIC_HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, head to your &lt;code&gt;_app.js&lt;/code&gt; file and use &lt;code&gt;configureAbly&lt;/code&gt; from &lt;code&gt;@ably-labs/react-hooks&lt;/code&gt; to call your new authentication endpoint:&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;configureAbly&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;@ably-labs/react-hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../styles/globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;configureAbly&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="s2"&gt;`&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;NEXT_PUBLIC_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/createTokenRequest`&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="nx"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your authentication is in place. Unfortunately, if your server is running, you might see a &lt;code&gt;SyntaxError: Unexpected token 'export' issue in your browser&lt;/code&gt;. The reason for that is because the &lt;code&gt;@ably-labs/react-hooks package&lt;/code&gt; exports as a ES6 module and Next.js doesn’t transpile functions imported from &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thankfully, there is an easy fix. First, install the &lt;code&gt;next-transpile-modules&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; next-transpile-modules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, head to the &lt;code&gt;next.config.js&lt;/code&gt; file in your root folder and specify which library should be transpiled:&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;withTM&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="s1"&gt;next-transpile-modules&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;@ably-labs/react-hooks&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;withTM&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;reactStrictMode&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll need to restart your server by hitting CTRL+C in your terminal and re-running &lt;code&gt;npm run dev&lt;/code&gt;. But the error should be gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Implement Pub/Sub
&lt;/h3&gt;

&lt;p&gt;Now that you have the Ably library installed, you can work on the realtime elements. &lt;/p&gt;

&lt;p&gt;Ably pub/sub is based on the concept of &lt;a href="https://ably.com/docs/core-features/channels"&gt;channels&lt;/a&gt;. Publishers send messages to a named channel and subscribers consume messages sent to that channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DTQ0nlYB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/06/pubsub-live-blog-post-comments%402x.png%3Ftr%3Dw-800" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DTQ0nlYB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/06/pubsub-live-blog-post-comments%402x.png%3Ftr%3Dw-800" alt="Ably pub/sub for comment updates" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Ably React Hooks NPM module provides a hook called &lt;code&gt;useChannel&lt;/code&gt;. This hook subscribes to a specified channel when the component mounts and unsubscribes from it when the component unmounts.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useChannel&lt;/code&gt; hook takes a callback function. This function is called every time a new message arrives on the channel so you can process it and make any necessary updates:&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;useChannel&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;@ably-labs/react-hooks&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="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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-channel-name&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;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="c1"&gt;//Process the incoming message&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s talk about how you’ll use this in your application.&lt;/p&gt;

&lt;p&gt;When a user submits a new comment on a blog post, your application will publish a message containing this comment to a channel called &lt;code&gt;comment-channel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Anyone else reading that blog post will see the new comment immediately, because they are subscribed to the channel and have therefore received and processed this new comment.&lt;/p&gt;

&lt;p&gt;To do this, head to your &lt;code&gt;Comments&lt;/code&gt; component and note that there is a state variable called &lt;code&gt;comments&lt;/code&gt;. This will store your list of comments. This variable gets passed to the &lt;code&gt;CommentsList&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;submitComment()&lt;/code&gt; function and use &lt;code&gt;channel.publish()&lt;/code&gt; to publish a new message to the channel. In Ably, a message has two parameters: &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt;. For the data, you only need the commenter’s &lt;code&gt;username&lt;/code&gt; and the &lt;code&gt;text&lt;/code&gt; of the comment itself. Pass this  &lt;code&gt;submitComment()&lt;/code&gt; function to your &lt;code&gt;AddCommentSection&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Once this message is pushed, everyone subscribed to this channel, including the person creating the comment, will receive it, and it will be added to the list of comments in your state variable, thanks to the callback in &lt;code&gt;useChannel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the code for &lt;code&gt;Comments.js&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;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useChannel&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;@ably-labs/react-hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Typography&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;@mui/material/Typography&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CommentsList&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;./CommentsList&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AddCommentSection&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;./AddCommentSection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Comments&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialComments&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="p"&gt;{&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;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setComments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialComments&lt;/span&gt;&lt;span class="p"&gt;);&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;channel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useChannel&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-channel&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;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="c1"&gt;// Add new incoming comment to the list of comments&lt;/span&gt;
    &lt;span class="nx"&gt;setComments&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="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="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="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;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitComment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;comment&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;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;body&lt;/span&gt; &lt;span class="o"&gt;=&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;comment&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;fetch&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;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;NEXT_PUBLIC_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/comment`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;body&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;publish&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&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="na"&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;comment&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;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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred when creating a comment: &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="k"&gt;return&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fragment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;gutterBottom&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Comments&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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CommentsList&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AddCommentSection&lt;/span&gt; &lt;span class="nx"&gt;submitComment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitComment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/React.Fragment&lt;/span&gt;&lt;span class="err"&gt;&amp;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;strong&gt;Note&lt;/strong&gt;: In this example, we're using Ably's React Hooks on the client to both publish and subscribe to a channel. In a production application, you would typically want publishing to be handled by the server. This would enable you to perform any required validation before a message is distributed to other users and persisted. For example, you might want to run the message through a profanity filter. You can use our &lt;a href="https://ably.com/docs/best-practice-guide#rest-or-realtime"&gt;REST SDK&lt;/a&gt; for server-side publishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Connect the comment form
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;AddCommentSection&lt;/code&gt; component, first receive the &lt;code&gt;Comment&lt;/code&gt; components &lt;code&gt;submitComment&lt;/code&gt; function as props.&lt;/p&gt;

&lt;p&gt;Create two new state variables: &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;comment&lt;/code&gt;. Then, replace the calls to &lt;code&gt;console.log()&lt;/code&gt;s in the component’s JSX markup with calls to the functions that set the username and state variables: &lt;code&gt;setUsername()&lt;/code&gt; and &lt;code&gt;setComment()&lt;/code&gt;, respectively. Also, set the corresponding &lt;code&gt;TextField&lt;/code&gt; &lt;code&gt;value&lt;/code&gt; attributes to &lt;code&gt;{username}&lt;/code&gt; and &lt;code&gt;{comment}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Finally, create an &lt;code&gt;addComment()&lt;/code&gt; function that will use your new &lt;code&gt;submitComment()&lt;/code&gt; function to publish the message and reset your form:&lt;/p&gt;

&lt;p&gt;Here is the code for &lt;code&gt;AddCommentSection.js&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;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Grid&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;@mui/material/Grid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TextField&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;@mui/material/TextField&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;FormControl&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;@mui/material/FormControl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Button&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;@mui/material/Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;AddCommentSection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;submitComment&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="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;setUsername&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setComment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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;addComment&lt;/span&gt; &lt;span class="o"&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="c1"&gt;//Publish message&lt;/span&gt;
    &lt;span class="nx"&gt;submitComment&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;comment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//Reset form&lt;/span&gt;
    &lt;span class="nx"&gt;setUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setComment&lt;/span&gt;&lt;span class="p"&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="k"&gt;return&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;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&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="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="nx"&gt;xs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextField&lt;/span&gt;
            &lt;span class="nx"&gt;required&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&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;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outlined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&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;onChange&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setUsername&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
          &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="nx"&gt;xs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextField&lt;/span&gt;
            &lt;span class="nx"&gt;required&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&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="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;comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&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="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outlined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;multiline&lt;/span&gt;
            &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;onChange&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setComment&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
          &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="nx"&gt;xs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contained&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;addComment&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;Submit&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FormControl&lt;/span&gt;&lt;span class="err"&gt;&amp;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;
  
  
  Step 7: Try it out!
&lt;/h3&gt;

&lt;p&gt;If your server isn’t already running, start it with &lt;code&gt;npm run dev&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Open &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; in two separate browser tabs and try submitting comments from each. If everything is working correctly, then you should see new comments appearing instantly in both tabs.&lt;/p&gt;

&lt;p&gt;The only issue is that if you refresh the page, you will lose all your comments. This is because your app is missing the last piece of the puzzle: data persistence. That’s what you’ll implement next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Create your PlanetScale database
&lt;/h3&gt;

&lt;p&gt;In this tutorial, you will be using PlanetScale for a hosted database in which to store blog comments. To start with, head to &lt;a href="https://planetscale.com/"&gt;https://planetscale.com/&lt;/a&gt; and create a free account. Once you’re logged in, click the &lt;strong&gt;New Database&lt;/strong&gt; button to create a database.&lt;/p&gt;

&lt;p&gt;In the dialog that appears, name the database &lt;code&gt;ably-realtime-db&lt;/code&gt; and choose the region closest to you to minimise latency.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Create Database&lt;/strong&gt; button and your database is ready to use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XCrMmeIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://files.ably.io/ghost/prod/2022/06/db-creation-a.png%3Ftr%3Dw-600" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XCrMmeIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://files.ably.io/ghost/prod/2022/06/db-creation-a.png%3Ftr%3Dw-600" alt="UI of PlanetScale database creation" width="645" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’re done, go back to your terminal and install the &lt;a href="https://github.com/planetscale/cli"&gt;PlanetScale CLI&lt;/a&gt;. This will enable you to run many helpful commands, such as opening a shell to query your database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Install and configure the Prisma ORM
&lt;/h3&gt;

&lt;p&gt;In your application’s root directory, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command does three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds a &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable to your &lt;code&gt;.env&lt;/code&gt; file with a dummy URL&lt;/li&gt;
&lt;li&gt;Creates a &lt;code&gt;prisma&lt;/code&gt; folder within your application&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;schema.prisma&lt;/code&gt; file inside the &lt;code&gt;prisma&lt;/code&gt; folder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;schema.prisma&lt;/code&gt; file configures your database URL and provider. You will also define your comment data model in here in a future step.&lt;/p&gt;

&lt;p&gt;Right now, however, the &lt;code&gt;DATABASE_URL&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; is a dummy one. So head back to the PlanetScale webpage. On the right side of your page, you should see a &lt;strong&gt;Connect&lt;/strong&gt; button. Click on it and then on &lt;strong&gt;Generate Password&lt;/strong&gt;. Select the &lt;em&gt;Prisma&lt;/em&gt; format and you should see something like this:&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;datasource&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mysql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;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;mysql://******:************@******.region.psdb.cloud/database-name?sslaccept=strict&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;Copy this URL and add it to your .env file, but &lt;em&gt;remove the double quotes&lt;/em&gt;, as this can cause problems with Vercel when you deploy your app in a later step:&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;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//****:****@***.region.psdb.cloud/ably-realtime-db?sslaccept=strict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your &lt;code&gt;schema.prisma&lt;/code&gt; file, change the provider to &lt;code&gt;mysql&lt;/code&gt; and enforce referential integrity as shown below. This step is necessary due to incompatibilities between Prisma and PlanetScale, because the latter doesn’t support foreign key constraints. To overcome this, we have to set the &lt;code&gt;referentialIntegrity&lt;/code&gt; property:&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;generator&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;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma-client-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;previewFeatures&lt;/span&gt; &lt;span class="o"&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;referentialIntegrity&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;datasource&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mysql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;referentialIntegrity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma&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;In this same file, define a data model for blog comments. It will be a very straightforward one with an autogenerated ID, a user name, and a comment:&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;model&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="nx"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;autoincrement&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;id&lt;/span&gt; 
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your model is in place, sync your schema to your PlanetScale database. To start, connect to your database by opening a new tab in the terminal and running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma db push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well, you should see the following message:&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="p"&gt;?&lt;/span&gt;  &lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;sync&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Done&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;996&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="err"&gt;✔&lt;/span&gt; &lt;span class="nx"&gt;Generated&lt;/span&gt; &lt;span class="nx"&gt;Prisma&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;library&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prisma&lt;/span&gt;&lt;span class="sr"&gt;/client in 122m&lt;/span&gt;&lt;span class="err"&gt;s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that your schema is working correctly by connecting to your database using the PlanetScale CLI. Specify the database name and the branch you are working on, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pscale shell ably-realtime-db starter-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open a shell into your database where you can execute &lt;code&gt;describe Comment;&lt;/code&gt; and see the following table definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ably-realtime-db/| starter-project |&amp;gt; describe Comment&lt;span class="p"&gt;;&lt;/span&gt;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| &lt;span class="nb"&gt;id&lt;/span&gt;       | int          | NO   | PRI | NULL    | auto_increment |
| username | varchar&lt;span class="o"&gt;(&lt;/span&gt;191&lt;span class="o"&gt;)&lt;/span&gt; | NO   |     | NULL    |                |
| comment  | varchar&lt;span class="o"&gt;(&lt;/span&gt;191&lt;span class="o"&gt;)&lt;/span&gt; | NO   |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;exit;&lt;/code&gt; to quit the shell.&lt;/p&gt;

&lt;p&gt;Great! Your Prisma schema is now in sync with PlanetScale. The last step is to promote your current PlanetScale branch to be the production branch. To do this, execute the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pscale branch promote ably-realtime-db starter-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 10: Install the Prisma client package
&lt;/h3&gt;

&lt;p&gt;You have defined the connection details and data model in &lt;code&gt;prisma.schema&lt;/code&gt;. The next step is to get your app to use this information to persist comment data.&lt;/p&gt;

&lt;p&gt;First, stop your server if it’s still running, and install the Prisma client package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @prisma/client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, instantiate the Prisma client. Create a folder called &lt;code&gt;lib&lt;/code&gt; and a file inside the folder called &lt;code&gt;prisma.js&lt;/code&gt; for this:&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;PrismaClient&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;@prisma/client&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;prisma&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;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 11: Create API routes for reading and writing comments to the database
&lt;/h3&gt;

&lt;p&gt;In the same way that you built the &lt;code&gt;/api/createTokenRequest&lt;/code&gt; route, create another route called &lt;code&gt;/api/comment&lt;/code&gt; that your application can make GET and POST requests to. GET reads the existing comments from the database and POST writes a new comment to the database.&lt;/p&gt;

&lt;p&gt;At the moment, the only methods you need to pay attention to are GET and POST. Anything else should return a 405 HTTP status.&lt;/p&gt;

&lt;p&gt;Your GET endpoint should return a list of all your comments using a simple Prisma query. For the POST endpoint, your username and comment are contained within the body of the request, which you will use to create a new comment in your table.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;/pages/api/comment.js&lt;/code&gt;, enter the following code:&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="nx"&gt;prisma&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;../../lib/prisma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&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;handleGET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;handlePOST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;default&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// GET /api/comment - retrieves all the comments&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;handleGET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="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;comments&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findMany&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;json&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;json&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;// POST /api/comment - creates a new comment&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;handlePOST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="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;comment&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;data&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;res&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;comment&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;status&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="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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;json&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;
  
  
  Step 12: Read existing comments from the database
&lt;/h3&gt;

&lt;p&gt;You can now make a GET request to your &lt;code&gt;/api/comment&lt;/code&gt; endpoint to fetch all the comments from your database. You can do that with getServerSideProps. This function is a really handy feature of Next.js that enables you to make an API call when the page is requested by the user. When the user first loads the page, the client will make an API call, fetch the saved comments, and pass them to props.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;pages/index.js&lt;/code&gt;, retrieve the comments from props and pass them to the &lt;code&gt;Comments&lt;/code&gt; component to populate your list:&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="nx"&gt;Container&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;@mui/material/Container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Grid&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;@mui/material/Grid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CssBaseline&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;@mui/material/CssBaseline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Divider&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;@mui/material/Divider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Header&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;../components/Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Footer&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;../components/Footer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Content&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;../components/Content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Comments&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;../components/Comments&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;Home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;comments&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="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="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CssBaseline&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="nx"&gt;maxWidth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;mt&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="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt;
            &lt;span class="nx"&gt;item&lt;/span&gt;
            &lt;span class="nx"&gt;xs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
              &lt;span class="na"&gt;py&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="p"&gt;}}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Divider&lt;/span&gt;
              &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;middle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="nx"&gt;sx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
                &lt;span class="na"&gt;my&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="p"&gt;}}&lt;/span&gt;
            &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Comments&lt;/span&gt; &lt;span class="nx"&gt;initialComments&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Footer&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&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;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;NEXT_PUBLIC_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/comment`&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;comments&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;res&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 13: Write new comments to the database
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;Comments&lt;/code&gt; component, you can now save a new comment from your &lt;code&gt;submitComment&lt;/code&gt; function. All you have to do is make a POST request with the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;comment&lt;/code&gt; in the request body.&lt;/p&gt;

&lt;p&gt;Then, when you publish the message, all the subscribers can be notified and updated accordingly:&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="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useChannel&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;@ably-labs/react-hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Typography&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;@mui/material/Typography&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CommentsList&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;./CommentsList&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AddCommentSection&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;./AddCommentSection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Comments&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialComments&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="p"&gt;{&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;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setComments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialComments&lt;/span&gt;&lt;span class="p"&gt;);&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;channel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useChannel&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-channel&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;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="c1"&gt;// Add new incoming comment to the list of comments&lt;/span&gt;
    &lt;span class="nx"&gt;setComments&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="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="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="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;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitComment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;comment&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;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;body&lt;/span&gt; &lt;span class="o"&gt;=&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;comment&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;fetch&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;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;NEXT_PUBLIC_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/comment`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;body&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;publish&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&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="na"&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;comment&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;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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred when creating a comment: &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="k"&gt;return&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fragment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Typography&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;gutterBottom&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Comments&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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Typography&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CommentsList&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AddCommentSection&lt;/span&gt; &lt;span class="nx"&gt;submitComment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitComment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/React.Fragment&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Run your server again using &lt;code&gt;npm run dev&lt;/code&gt; and try adding some comments.&lt;/p&gt;

&lt;p&gt;Then, open another terminal window and execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open &lt;a href="http://localhost:5555/"&gt;http://localhost:5555/&lt;/a&gt; in a new browser tab where you can see and manipulate the contents of your Comment table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w9-ddBRs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic5.png%3Ftr%3Dw-800" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w9-ddBRs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic5.png%3Ftr%3Dw-800" alt="Prisma Studio showing some data in the comments table" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! You are now ready to share the results of your work with the world. You’ll do that in the final step, by deploying your application to Vercel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 14: Deploy your app to Vercel
&lt;/h3&gt;

&lt;p&gt;Before you deploy your app, you’ll have to do a bit of tidy up to deal with the inevitable CORS issues that arise when you host any web application.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;next.config.js&lt;/code&gt;, add the following HTTP headers. These specify which origins, methods, and other features your requests will support:&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;withTM&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;next-transpile-modules&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;@ably-labs/react-hooks&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;withTM&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;reactStrictMode&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="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;headers&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="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// match all API routes&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/:path*&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&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;key&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="na"&gt;value&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="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="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="na"&gt;value&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,OPTIONS,PATCH,DELETE,POST,PUT&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="na"&gt;key&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version&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;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;You also need to support the OPTIONS method, which is used as a preflight check in API requests. In your &lt;code&gt;pages/api/comment.js&lt;/code&gt; file, add handling for OPTIONS by returning HTTP status 200:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&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;handleGET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;handlePOST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;default&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you’re happy that your application is working correctly, commit and push your &lt;code&gt;starter-project&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout starter-project
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Final code"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don’t already have one, &lt;a href="https://vercel.com/signup"&gt;create an account with Vercel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you’re logged in, click on New Project, and import the Git repository where your code lives. Copy the URL that Vercel has generated for you, which should be something like &lt;a href="https://%5Bproject-name%5D.vercel.app/"&gt;https://[project-name].vercel.app/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Edit your &lt;code&gt;.env&lt;/code&gt; file &lt;code&gt;NEXT_PUBLIC_HOSTNAME&lt;/code&gt; setting to point to the Vercel URL instead of &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;. Commit and push the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Configure deployment URL"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re currently still working on the &lt;code&gt;starter-project&lt;/code&gt; branch, so for now set that as the Production branch in Vercel.&lt;/p&gt;

&lt;p&gt;To do this, click your Github project in the Overview page, select the &lt;strong&gt;Settings&lt;/strong&gt; tab and then the &lt;strong&gt;Git&lt;/strong&gt; page from the left-hand navigation menu. Change the Production Branch to &lt;code&gt;starter-project&lt;/code&gt;, then click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then, visit the Deployments tab, click the three little dots to the right of your deployment and select &lt;strong&gt;Redeploy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once the deployment is complete, your app should be up and running!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pSDTEWOb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic6.gif%3Ftr%3Dw-800" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pSDTEWOb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://ik.imagekit.io/ably/ghost/prod/2022/05/graphic6.gif%3Ftr%3Dw-800" alt="Displaying realtime communication in a live environment" width="600" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this tutorial, you learned how to implement pub/sub messaging in realtime using the Ably platform within a Next.js application. You also learned how to create a database with PlanetScale and how to fetch and write data to it with the Prisma ORM. Finally, you used Vercel to deploy your application in a live environment.&lt;/p&gt;

&lt;p&gt;Realtime communication might still be new for some developers, but the public is increasingly demanding instant feedback on all interactions they have online. You’re now in a great position to give them what they want, whatever the nature of your application or service.&lt;/p&gt;

&lt;p&gt;We’d like to acknowledge the significant contribution to this article by &lt;a href="https://www.linkedin.com/in/devtechwriter/"&gt;Mark Lewin&lt;/a&gt;, who is a Senior Developer Educator here at Ably.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>database</category>
    </item>
    <item>
      <title>Share your views on development in 2019</title>
      <dc:creator>Jo Stichbury</dc:creator>
      <pubDate>Thu, 10 Jan 2019 16:28:08 +0000</pubDate>
      <link>https://forem.com/stichbury/share-your-views-on-development-in-2019-loh</link>
      <guid>https://forem.com/stichbury/share-your-views-on-development-in-2019-loh</guid>
      <description>&lt;p&gt;Hi there! I've just joined the community and am looking forward to contributing some thoughts on coding with graph databases and AI. But that's for next week :o)&lt;/p&gt;

&lt;p&gt;Today, I just wanted to do a quick shoutout for &lt;a href="https://slashdata.co"&gt;SlashData&lt;/a&gt;, a company that I work with from time to time (we recently published a book on how to market to developers ... clue: don't patronise or hype). In the spirit of that message, I promise that I won't be pushing many of this kind of post onto DEV, but I did want to share this survey before it closes. &lt;/p&gt;

&lt;p&gt;You may have come across SlashData before. They are an analysis company that tracks global software developer trends via regular, large scale developer surveys.&lt;/p&gt;

&lt;p&gt;Those answering the surveys provide data for research that help the top technology firms understand who developers are, what tools are they using and where they‘re going next.&lt;/p&gt;

&lt;p&gt;SlashData have just launched the 16th edition of our Developer Economics Survey. For everyone that takes the survey SlashData donates to a charity. This month, they're aiming to donate $2,000 to help get more kids into software development.&lt;/p&gt;

&lt;p&gt;If you're interested in sharing your views on software development, or know someone who may be, please check out the survey &lt;a href="https://survey.developereconomics.com/surveys/de16?utm_source=owned-community&amp;amp;utm_medium=owned&amp;amp;utm_campaign=AR_VR_article"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>survey</category>
      <category>opinions</category>
      <category>charity</category>
    </item>
  </channel>
</rss>
