<?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: Shayan</title>
    <description>The latest articles on Forem by Shayan (@sh4yy).</description>
    <link>https://forem.com/sh4yy</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%2F637172%2F4ec0f03a-fdca-4616-b9d2-91d9e892192f.png</url>
      <title>Forem: Shayan</title>
      <link>https://forem.com/sh4yy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sh4yy"/>
    <language>en</language>
    <item>
      <title>🚀 Top 11 Canny Alternatives in 2025</title>
      <dc:creator>Shayan</dc:creator>
      <pubDate>Wed, 11 Dec 2024 16:46:40 +0000</pubDate>
      <link>https://forem.com/sh4yy/top-11-canny-alternatives-in-2025-924</link>
      <guid>https://forem.com/sh4yy/top-11-canny-alternatives-in-2025-924</guid>
      <description>&lt;p&gt;If you're looking for alternatives to Canny for collecting and managing user feedback, I have complied a list of 11 alternatives that you can consider.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. UserJot
&lt;/h2&gt;

&lt;p&gt;Full disclosure: I'm the founder of UserJot. UserJot is a simple feedback management tool that's great for startups. It's has a clean interface and provides everything you need to collect and manage feedback, including a roadmap and changelog. The pricing is affordable, and provides a good free tier for smaller teams. It's a good alternative to Canny if you want a more modern and feature-rich tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://userjot.com" rel="noopener noreferrer"&gt;https://userjot.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwmd3rfaaq2u7f5jjv244.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwmd3rfaaq2u7f5jjv244.png" alt="UserJot landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. UserVoice
&lt;/h2&gt;

&lt;p&gt;UserVoice offers a feedback management platform that's been around for a while. It's got some decent analytics tools and can integrate with quite a few other services. The interface is functional, though it might take some getting used to. It's a solid choice if you need something comprehensive, but it might be overkill for smaller teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://uservoice.com" rel="noopener noreferrer"&gt;https://uservoice.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmjjzdmbbh54mh21298hg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmjjzdmbbh54mh21298hg.png" alt="UserVoice landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Productboard
&lt;/h2&gt;

&lt;p&gt;Productboard is more of an all-round product management tool that includes feedback collection. It's pretty good at helping you organize and prioritize ideas based on user input. The visual layouts are nice, but some users find the learning curve a bit steep. It's worth considering if you want feedback management as part of a larger product suite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://productboard.com" rel="noopener noreferrer"&gt;https://productboard.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwtx9anh8v9ve2g0itfia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwtx9anh8v9ve2g0itfia.png" alt="Productboard landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Aha!
&lt;/h2&gt;

&lt;p&gt;Aha! is another full-featured product management platform. It's got modules for idea management and roadmapping, which can be handy. The tool is quite extensive, which means it can do a lot, but also that it might be more complex than what some teams need. It's a bit pricey, so it's probably better suited for larger organizations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://aha.io" rel="noopener noreferrer"&gt;https://aha.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fn1ebzb8hyhjlktgx2sna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fn1ebzb8hyhjlktgx2sna.png" alt="Aha! landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Upvoty
&lt;/h2&gt;

&lt;p&gt;Upvoty focuses more specifically on feedback collection and management. It's got a clean interface that makes it easy for users to submit and vote on ideas. The roadmap feature is pretty straightforward. It's a good option if you want something simpler than the big product management suites, though it might lack some advanced features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://upvoty.com" rel="noopener noreferrer"&gt;https://upvoty.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fq4kopr05iryefwp277ur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fq4kopr05iryefwp277ur.png" alt="Upvoty landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. FeatureOS
&lt;/h2&gt;

&lt;p&gt;FeatureOS tries to cover all the bases with feedback boards, roadmaps, and changelog features. It's got a modern look and feel, which is nice. The pricing is reasonable for what you get. Some users mention that certain features could be more fleshed out, but overall it's a decent all-in-one option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://featureos.app" rel="noopener noreferrer"&gt;https://featureos.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fv5k2xq83b352z6s7699b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fv5k2xq83b352z6s7699b.png" alt="FeatureOS landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Frill
&lt;/h2&gt;

&lt;p&gt;Frill keeps things relatively simple with its feedback management and roadmap tools. The voting system is straightforward, which users tend to appreciate. It's not as feature-rich as some other options, but that might be a plus if you're after something more streamlined. The pricing is pretty competitive too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://frill.co" rel="noopener noreferrer"&gt;https://frill.co&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyaddh0qxhtmu6qq6a2m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyaddh0qxhtmu6qq6a2m4.png" alt="Frill landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Feedbear
&lt;/h2&gt;

&lt;p&gt;Feedbear offers a no-frills approach to feedback collection and management. It's got the basics covered with boards, roadmaps, and announcements. The interface is clean, if a bit basic. It's a good choice if you want something easy to set up and use, though power users might find it lacking in some areas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://feedbear.com" rel="noopener noreferrer"&gt;https://feedbear.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1b7e74z0vl6kb6e3akmx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1b7e74z0vl6kb6e3akmx.png" alt="Feedbear landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Nolt
&lt;/h2&gt;

&lt;p&gt;Nolt keeps things really simple with its feedback boards. Users can submit and vote on ideas, and that's pretty much it. The minimalist approach means it's easy to get started with, but you might outgrow it if you need more advanced features. It's worth a look if you just want a basic voting board without the bells and whistles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://nolt.io" rel="noopener noreferrer"&gt;https://nolt.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F14hx5poh7bnplesuu7dp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F14hx5poh7bnplesuu7dp.png" alt="Nolt landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Usersnap
&lt;/h2&gt;

&lt;p&gt;Usersnap takes a slightly different approach by focusing on visual feedback. Users can annotate screenshots, which can be helpful for certain types of products. It also has more traditional feedback management features. The visual element is nice, but it might not be necessary for all types of feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://usersnap.com" rel="noopener noreferrer"&gt;https://usersnap.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft6ua0qt1nst0b5x3cq0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft6ua0qt1nst0b5x3cq0m.png" alt="Usersnap landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Feature Upvote
&lt;/h2&gt;

&lt;p&gt;Feature Upvote does what it says on the tin - it lets users suggest and vote on features. It's pretty bare-bones, which can be good or bad depending on your needs. The customization options are decent. It's worth considering if you just need a simple voting mechanism without much else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://featureupvote.com" rel="noopener noreferrer"&gt;https://featureupvote.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxmqvfl5wmgl9u1uly5l4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxmqvfl5wmgl9u1uly5l4.png" alt="Feature Upvote landing page" width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember, the best tool depends on your specific needs, team size, and budget. It's worth trying out a few to see which one feels right for your workflow.&lt;/p&gt;

</description>
      <category>productmanagement</category>
      <category>startup</category>
      <category>marketing</category>
      <category>changelog</category>
    </item>
    <item>
      <title>Get push notifications from Javascript in just one minute!</title>
      <dc:creator>Shayan</dc:creator>
      <pubDate>Thu, 27 Jan 2022 20:36:30 +0000</pubDate>
      <link>https://forem.com/sh4yy/get-push-notifications-from-javascript-in-just-one-minute-2mke</link>
      <guid>https://forem.com/sh4yy/get-push-notifications-from-javascript-in-just-one-minute-2mke</guid>
      <description>&lt;p&gt;There have been many times when, as a developer, I wanted to be notified and track certain events that happened within my projects. For example, when a user joins a newsletter, creates an account, upgrades to a premium plan, or provides feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logsnag.com" rel="noopener noreferrer"&gt;LogSnag&lt;/a&gt; makes it very easy to set up these notifications and creates feeds of events so you can be aware of what happened and when it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;First, I will add a new project to my LogSnag account. Let's call it &lt;em&gt;&lt;strong&gt;my-saas&lt;/strong&gt;&lt;/em&gt; for this example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fww1ff2tskvxyyeext5qk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fww1ff2tskvxyyeext5qk.gif" alt="Create a new project" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Next, we need an API token. Head to the settings, open the API tab, and use the + button to create a new token. You can then use the clipboard icon to copy the token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fel0hsxttpts3nmhbtvv9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fel0hsxttpts3nmhbtvv9.gif" alt="Copy your API Token" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are almost done! Let's move to our code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Javascript Time!
&lt;/h2&gt;

&lt;p&gt;First, let's install the &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Flogsnag" rel="noopener noreferrer"&gt;LogSnag npm package&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install --save logsnag&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Then, we have to import the package and initialize our client with the API token that we just copied from the application.&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;LogSnag&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="s1"&gt;logsnag&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;logsnag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogSnag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MY_API_TOKEN&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;Finally, we can use our client to publish any events from our application.&lt;/p&gt;

&lt;p&gt;For this example, I will call my channel &lt;em&gt;&lt;strong&gt;waitlist&lt;/strong&gt;&lt;/em&gt; as I would like to be notified and keep track of users who join my waitlist. Since this is the first time we publish to this channel, LogSnag will automatically create it for us.&lt;/p&gt;

&lt;p&gt;I'm going to pass in the user email in the description and use the unicorn emoji as the icon. Most importantly, I will set notify to true since I would like to receive a push notification for this event.&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;logsnag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-saas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;waitlist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User Joined Waitlist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email: john.doe@yahoo.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;icon&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="na"&gt;notify&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we run this code, a new channel is created under the &lt;strong&gt;&lt;em&gt;my-saas&lt;/em&gt;&lt;/strong&gt; project and we get push notifications for this event on all of the devices that have &lt;a href="https://logsnag.com" rel="noopener noreferrer"&gt;LogSnag&lt;/a&gt; installed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsd5hjzfpyss2bc0zu1fi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsd5hjzfpyss2bc0zu1fi.gif" alt="LogSnag Notification" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LogSnag has been a side project for the past couple of months. It has originated from the pain points of using messaging platforms to publish and track user activity and events. LogSnag has been explicitly designed for this purpose and provides powerful features that make it much easier to track events and projects. Currently, LogSnag is in the beta stage, and you can get access by signing up for the &lt;a href="https://logsnag.com" rel="noopener noreferrer"&gt;waitlist on the website&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>notification</category>
      <category>webdev</category>
      <category>npm</category>
    </item>
    <item>
      <title>How to Send your Events and Logs to Discord via Python or JavaScript</title>
      <dc:creator>Shayan</dc:creator>
      <pubDate>Mon, 15 Nov 2021 18:29:43 +0000</pubDate>
      <link>https://forem.com/sh4yy/how-to-send-your-events-and-logs-to-discord-via-python-or-javascript-13li</link>
      <guid>https://forem.com/sh4yy/how-to-send-your-events-and-logs-to-discord-via-python-or-javascript-13li</guid>
      <description>&lt;p&gt;Update: I have recently started working on a new project to keep track of events from my projects which I use now over Discord. I would love to hear your feedback on the project: &lt;a href="https://logsnag.com" rel="noopener noreferrer"&gt;LogSnag - Track your projects' events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Throughout my software development career, there were many times when I wanted to get instant updates and alerts when something happened within my project.&lt;/p&gt;

&lt;p&gt;Take this, for example; you are developing a SaaS, and there are multiple valuable events that you would like to be aware of as soon as they happen. For example, users joining your waitlist or newsletter, user sign-ups, product sales, and user conversions. Having to run a long training or crawling task on a remote machine was another situation where I wanted instant updates on the progress and if something had gone wrong. I mean, you can do periodic checks on the machine to see how things are going, but I’d rather entirely forget about it and have it send me updates instead. The last example is when I wanted to automate my garage door via raspberry pi and I wanted to know when the garage door was opening, closing, or had been left open for too long.&lt;/p&gt;

&lt;p&gt;So why am I telling you these examples? I want you to start thinking about similar situations that you may also need a way to send yourself instant updates and to have a history of all these events in a single place.&lt;/p&gt;

&lt;p&gt;Discord makes it relatively easy to solve this problem! We can create a Discord server specifically, create separate channels for our projects and use the webhook URL to push our events!&lt;/p&gt;

&lt;p&gt;To get started, we need that Discord server. Log in to Discord, click on the &lt;strong&gt;Add Server&lt;/strong&gt; button and proceed to create your own. Once that’s done, you should be able to see and open your Discord server&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fs71ygtxq7sf9sy4bdnai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fs71ygtxq7sf9sy4bdnai.png" alt="Discord Server" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make it easier to organize my projects and events, I usually create a new text channel for each project or, in some cases, one per type of event. To do so, click the &lt;strong&gt;+&lt;/strong&gt; button next to text channels and create one. I’m going to call mine &lt;strong&gt;garage-door&lt;/strong&gt; and make it a private channel.&lt;/p&gt;

&lt;p&gt;Then, click on the settings icon for that channel, open the &lt;strong&gt;integration&lt;/strong&gt; tab and create a new Webhook. Once added, you can see the &lt;strong&gt;Copy Webhook URL&lt;/strong&gt;, and that’s precisely what we need to push our events! So copy that and paste it somewhere safe on your machine. Now, we are pretty much done with Discord, and we can move to writing some code! I have provided examples for Python and JavaScript; feel free to skip to whichever you find more relevant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fl2sbfxuzycqb27aky73c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fl2sbfxuzycqb27aky73c.png" alt="Discord Server" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending events via Python
&lt;/h2&gt;

&lt;p&gt;First, I would like to pass in my webhook URL as an environment variable, so I will set it to &lt;strong&gt;WEBHOOK_URL&lt;/strong&gt; and use the standard library in python to access the value. You can, of course, skip this entire process and add your URL directly to the code.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export WEBHOOK_URL=https://discord.com/api/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we have set the environment variable, we can access it by importing &lt;strong&gt;os&lt;/strong&gt; and using the &lt;strong&gt;os.environ.get&lt;/strong&gt; method.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os

WEBHOOK_URL = os.environ.get('WEBHOOK_URL')
print(WEBHOOK_URL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I will be using the &lt;a href="https://pypi.org/project/requests/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; package to handle my HTTP requests. You can install via PyPi by running the following command.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, all we need to do is to import &lt;strong&gt;requests&lt;/strong&gt; and make a &lt;strong&gt;POST&lt;/strong&gt; request to the webhook url and pass our event in the JSON body with the &lt;strong&gt;content&lt;/strong&gt; key.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests

requests.post(WEBHOOK_URL, { "content": "🦄 garage door is open" })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we run this code, we should get a new message in the &lt;strong&gt;garage-door&lt;/strong&gt; channel telling us that our garage door has been opened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpvbgnl68vf0a6eerskw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpvbgnl68vf0a6eerskw6.png" alt="Discord Message" width="800" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending events via JavaScript
&lt;/h2&gt;

&lt;p&gt;The process here is very similar to what we did in Python. First, define a new environment variable called &lt;strong&gt;WEBHOOK_URL&lt;/strong&gt; for the webhook URL that we copied from discord. Again, you can skip this step and directly set the URL to a variable.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export WEBHOOK_URL=https://discord.com/api/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, read this value &lt;strong&gt;process.env.WEBHOOK_URL&lt;/strong&gt; and set it to a variable.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const WEBHOOK_URL = process.env.WEBHOOK_URL
console.log(WEBHOOK_URL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I will be using the &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;Axios&lt;/a&gt; package to handle my HTTP requests. You can install via NPM by running the following command.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, we can import &lt;strong&gt;Axios&lt;/strong&gt; and make a &lt;strong&gt;POST&lt;/strong&gt; request to the webhook url and pass in our event in the JSON body.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios';

await axios.post(WEBHOOK_URL, { content: "💰 User sign up" })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we run this code, we should see another message in our Discord channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzqwt97na327yzi626a2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzqwt97na327yzi626a2u.png" alt="Discord Message" width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, getting this set up is very easy while being quite powerful! I use this setup in almost all of my projects to be aware of how they are doing!&lt;/p&gt;

</description>
      <category>discord</category>
      <category>python</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>Use Python or JavaScript to Send Your Events and Logs to Telegram via their Chatbot API</title>
      <dc:creator>Shayan</dc:creator>
      <pubDate>Sat, 13 Nov 2021 17:54:56 +0000</pubDate>
      <link>https://forem.com/sh4yy/use-python-or-javascript-to-send-your-events-and-logs-to-telegram-via-their-chatbot-api-3243</link>
      <guid>https://forem.com/sh4yy/use-python-or-javascript-to-send-your-events-and-logs-to-telegram-via-their-chatbot-api-3243</guid>
      <description>&lt;p&gt;Update: I have recently started working on a new project to keep track of events from my projects which I use now over Telegram. I would love to hear your feedback on the project: &lt;a href="https://logsnag.com" rel="noopener noreferrer"&gt;LogSnag - Track your projects' events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my opinion, Telegram has one of the best, if not the best, chatbot API. What they have created is extremely easy to use, yet it is unbelievably powerful. And I have used it for dozens of small projects over the years. One of my prominent use cases for their API is to send myself events and logs from my applications and projects.&lt;/p&gt;

&lt;p&gt;Consider this example; you are developing a new project for which you may need to crawl hundreds or thousands of web pages or any other similar long-running tasks. You would deploy this script to a VM, or maybe your machine, and let it run for the next day or two. But, how do you usually check on the progress? Do you ssh to the VM, find the right tmux session, and read the logs? What if you want to leave home and don’t have access to the computer? Well, this is one of those situations that made me think… “hmm, what if I could use Telegram as a remote output?” I mean, the API is very, very easy to use, and all I need to do is to make an HTTP request to their endpoint, so why not?&lt;/p&gt;

&lt;p&gt;This is just one of the dozens of examples where I found it convenient to push events to myself, and ever since, I have used it for many different projects to update myself on user actions, product sales, hell even stock prices. So in this blog post, I will be going over how to set up your first chatbot and how to send events to yourself via Python or JavaScript :)&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Register a Telegram Chatbot.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Alright, the first step is to message &lt;a href="https://t.me/botfather" rel="noopener noreferrer"&gt;@botfather&lt;/a&gt; on Telegram and to create your first chatbot! The process is very straightforward, as you can see in the following screenshot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzp4gemz8egr11gcq4qgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzp4gemz8egr11gcq4qgi.png" alt="Botfather Example" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s copy the API token and add it to our environment variables. You can, of course, skip this process and add it directly to your code. Also, make sure to keep this token private! I will be revoking mine after writing this post.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export BOT_TOKEN=2104030722:AAGdY_FeAFqvriecqv3lhissc-uG4t0arL4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Find your telegram Chat Id
&lt;/h3&gt;

&lt;p&gt;Ok, we have one more step to do before getting our hands dirty with some code. Thankfully this one is straightforward, we have to find our &lt;strong&gt;chat_id&lt;/strong&gt; in Telegram, and there are multiple ways to find it. I will be using the chatbot API to find out my &lt;strong&gt;chat_id&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, open the chatbot we have just created and send it a message; it could be anything. Then, make a get request to the following URL with your API token, using your browser or postman, and it should tell you your &lt;strong&gt;chat_id&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// template
[https://api.telegram.org/bot](https://api.telegram.org/bot)&amp;lt;BOT_TOKEN&amp;gt;/getUpdates

// how mine looks like with my bot token
[https://api.telegram.org/bot2104030722:AAGdY_FeAFqvriecqv3lhissc-uG4t0arL4/getUpdates](https://api.telegram.org/bot2104030722:AAGdY_FeAFqvriecqv3lhissc-uG4t0arL4/getUpdates)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwobyr0wp5xddjcx7qx4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwobyr0wp5xddjcx7qx4n.png" alt="Postman example" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you see in the screenshot, you can see your &lt;strong&gt;chat_id&lt;/strong&gt; via the following path &lt;strong&gt;result[].message.chat.id&lt;/strong&gt;. Copy and again, add it to your environment variables or paste directly in the code.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export CHAT_ID=&amp;lt;MY_CHAT_ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ok, that’s pretty much all we had to do with Telegram; let’s write some code. For this post, I will be providing an example with Python and JavaScript, so feel free to skip to whichever one you prefer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing Events via Python
&lt;/h3&gt;

&lt;p&gt;First, let’s access our environment variables and assign them to variables&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os

BOT_TOKEN = os.environ.get('BOT_TOKEN')
CHAT_ID = os.environ.get('CHAT_ID')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I will be using the &lt;a href="https://pypi.org/project/requests/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; package to handle my HTTP requests. So, let’s also install that via PyPi by running the following command.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Awesome! Now let’s define a method for sending our events to Telegram. We’re going to call it &lt;strong&gt;send_message&lt;/strong&gt;, and it will need to accept a message string.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def send_message(msg):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, we will add Telegram’s send message endpoint and populate it via our bot token.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url = f”https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, let’s define the URL parameters that we need to send to Telegram. We need a chat_id, and text and by now, we should have both ready.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;params = { “chat_id”: CHAT_ID, “text”: msg }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, we will import requests and make a GET request and pass in our URL parameters.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests.get(url, params=params)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Putting these all together, our method should look like the following.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests

def send_message(msg):
   url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
   params = { "chat_id": CHAT_ID, "text": msg }
   requests.get(url, params=params)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s pretty much it :) Now we can send whatever event that we have to ourselves through Telegram. So, remember the crawler job? Imagine we want to send periodic progress. We can do something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;send_message(“🔥 Crawling progress: 56% done”)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we run our code, if everything goes right, we should see something like this. How cool is that?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgsu270gfd8egncwcdt9b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgsu270gfd8egncwcdt9b.png" alt="Telegram messages example" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing Events via JavaScript
&lt;/h3&gt;

&lt;p&gt;Ok, let’s repeat the same process in JavaScript. First, let’s access our environment variables and set them to variables.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const BOT_TOKEN = process.env.BOT_TOKEN
const CHAT_ID = process.env.CHAT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, I will be using the &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;Axios&lt;/a&gt; package to handle my HTTP requests. You can install via NPM by running the following command.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let’s define our sendMessage method. It needs to take our string message as an argument. Then we will add the endpoint URL and populate it via our &lt;strong&gt;BOT_TOKEN&lt;/strong&gt;. Next, let’s define our URL parameters required by Telegram; &lt;strong&gt;chat_id&lt;/strong&gt; and &lt;strong&gt;text&lt;/strong&gt;. Finally, we can make our get request via Axios and pass in our params.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios';

const BOT_TOKEN = process.env.BOT_TOKEN
const CHAT_ID = process.env.CHAT_ID

async function sendMessage(message) {
    const url = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`
    const params = { chat_id: CHAT_ID, text: message }
    await axios.get(url, { params: params })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s pretty much it! Let’s give this one a shot as well.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await sendMessage("💰 New user signed up")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fhvfrzckqxt25usnr3g05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fhvfrzckqxt25usnr3g05.png" alt="Telegram messages example" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s pretty much it! By adding these couple of lines, we get another superpower that we can use for almost anything!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>python</category>
      <category>telegram</category>
      <category>api</category>
    </item>
    <item>
      <title>How I Created an Event-Driven Backend with RxJS, Server-Sent Events, Express and NodeJS</title>
      <dc:creator>Shayan</dc:creator>
      <pubDate>Sat, 29 May 2021 20:36:28 +0000</pubDate>
      <link>https://forem.com/sh4yy/how-i-created-an-event-driven-backend-with-rxjs-server-sent-events-and-expressjs-4d6k</link>
      <guid>https://forem.com/sh4yy/how-i-created-an-event-driven-backend-with-rxjs-server-sent-events-and-expressjs-4d6k</guid>
      <description>&lt;p&gt;About a month ago, a friend and I came up with an idea for a small website and decided to create an MVP in a couple of days to give it a shot. The idea was pretty simple; a meme-driven chat room and a live price chart for each cryptocurrency. I was assigned to create the backend for the project, and my friend would make the web client.&lt;/p&gt;

&lt;p&gt;I set three requirements for myself before starting to work on the server. First and foremost, I wanted to get the MVP out as soon as possible. Secondly, I wanted to make the server as lightweight as possible to just leave it running on a cheap VM. Lastly, I wanted to design the architecture to allow for easy scaling if the shit coin investors decide to make another stupid decision and use our application.&lt;/p&gt;

&lt;p&gt;The first step was to think of the entire server as a pipeline. Basically, the whole thing is a pipeline that consumes a set of events, processes them, and then streams them to many clients.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft07myzizo5mjmnayc2ca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft07myzizo5mjmnayc2ca.png" alt="Alt Text" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any incoming data can be considered as some sort of event. For example, we have things like users joining or leaving a room, publishing or deleting comments, and ticker price updates. On the other side, anything that consumes these events can be considered as a client; our database, cache, and every connected browser.&lt;/p&gt;

&lt;p&gt;After giving this a bit more thought, the first thing that came to my mind was a combination of &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; and Server-Sent Events. I have never written a server-side javascript project as I mainly use Golang and Python, but I really wanted to experiment with &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; on the server-side, and this seemed to be the perfect time to give it a shot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6a5nnjz1dxvwhm4mdd7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6a5nnjz1dxvwhm4mdd7z.png" alt="Alt Text" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To explain the implementation in more detail, I will walk through implementing a very simple stock/cryptocurrency live price streaming endpoint. First, we need a RxJS Subject which is a multicast observable. Our subject will take care of streaming the ticker price events to all of our clients (subscribers).&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;Subject&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="s1"&gt;rxjs&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;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&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="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// our RxJS subject&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TickerSubject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need an entry point for our events to be pushed to our stream. We can either emit directly to our subject or create a wrapper function as an abstraction layer and sanitize and check our data before emitting new events.&lt;/p&gt;

&lt;p&gt;To do so, let's define a function called &lt;code&gt;EmitTickerPrice&lt;/code&gt;. Every time we get a new ticker price data, we will call this method with the proper parameters, and it will emit a new event to our ticker subject.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Emit a new ticker price
 * @param {string} symbol: ticker symbol
 * @param {string} price: ticker price
 * @param {string} currency: ticker currency
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EmitTickerPrice&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;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&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;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;TickerSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticker&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;ticker&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;For our project, I am using PostgreSQL to persist historical ticker price information. I am also using Redis as a cache store to reduce the database load when clients request the data to render the price chart. As I mentioned previously, each of these is considered a client and independently subscribed to our &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; subjects. We can call subscribe on our ticker subject and pass a callback method to observe and handle each incoming event.&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;TickerSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty easy, right? Well, not really. See, for our website, we are pushing a new ticker price event every five seconds for each supported stock and cryptocurrency ticker. These events are also not synchronized and come in at different intervals, which means that we get dozens of ticker events every second. The problem is that we don't want to call our Redis and PostgreSQL subscriber callback every time a new event is emitted. Instead, we want to implement some additional logic in our pipeline to reduce the load on these services.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL Observer
&lt;/h3&gt;

&lt;p&gt;Let's start with PostgreSQL; inserting a new row individually each time a new ticker price is emitted is not ideal. This may differ for the different projects as, in some cases, we may need atomic inserts. However, for this project, the 30 seconds insertion delay was negligible. Luckily, &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; makes it very easy to implement this feature by providing pipelines and dozens of operators. For our case, we can create a pipe and use the &lt;code&gt;bufferTime&lt;/code&gt; operator to buffer our events for 30,000 milliseconds. Then, we can subscribe to the newly defined pipeline.&lt;/p&gt;

&lt;p&gt;Let's start with PostgreSQL; inserting a new row individually each time a new ticker price is emitted is not ideal. This may differ for the different projects as, in some cases, we may need atomic inserts. However, for this project, the 30 seconds insertion delay was negligible. Luckily, &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; makes it very easy to implement this feature by providing pipelines and dozens of operators. For our case, we can create a pipe and use the bufferTime operator to buffer our events for 30,000 milliseconds. Then, we can subscribe to the newly defined pipeline.&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;bufferTime&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="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;TickerSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TickerSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;bufferTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tickers&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our subscriber is called every 30 seconds, and it gets a list of buffered events in the past buffer period instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis Observer
&lt;/h3&gt;

&lt;p&gt;Our problem gets a bit more interesting with Redis. As I mentioned previously, Redis is mainly used to cache the price points needed to generate the price chart displayed on the website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7ijmi41hpowscg6m3nhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7ijmi41hpowscg6m3nhp.png" alt="Alt Text" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart is created for the different intervals such as the past 5 minutes, one hour, or a day. As you can tell by now, we don't need a data point every 5 seconds for our 24-hour chart; instead, a data point every 30 minutes or even an hour would do the job.&lt;/p&gt;

&lt;p&gt;Our Redis observer should throttle each unique ticker symbol for 30 minutes before calling the subscriber. To achieve this, we need to create a bit more complicated pipeline than what we previously had for the PostgreSQL observer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fp5wuwkzw95hb8zqyeii8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fp5wuwkzw95hb8zqyeii8.png" alt="Alt Text" width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we have to group our events based on their ticker symbol. To do so, we can use the &lt;code&gt;groupBy&lt;/code&gt; operator provided by &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; and provide an arrow function to specify how we are grouping these events. We want the group our events based on their ticker symbols; hence, we return the ticker symbol value from our arrow function.&lt;/p&gt;

&lt;p&gt;Next, we will throttle each group to emit once every 30 minutes and finally merge all the groups into a single pipeline. We can use the &lt;code&gt;mergeMap&lt;/code&gt; operator and map through each group to add the &lt;code&gt;throttleTime&lt;/code&gt; operator with a 30-minute interval. Finally, we can subscribe to the pipeline and insert the data into our Redis server.&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;groupBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;throttleTime&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="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;TickerSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ticker&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;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;group&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;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;throttleTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can even go further and buffer these events to take advantage of Redis pipelines, but I will skip that part as it will look almost identical to what we did with our PostgreSQL pipeline.&lt;/p&gt;

&lt;p&gt;If you made it thus far, pat yourself on the back, take a deep breath and go get some coffee before we get our hands dirty with server-sent events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-Sent Events Endpoint
&lt;/h3&gt;

&lt;p&gt;For our website, I am using ExpressJS and the @awaitjs/express library to use async/await in my routers. Register the path &lt;code&gt;/ticker/:symbol/event&lt;/code&gt; via &lt;code&gt;GET&lt;/code&gt; method on our express server to create our server-sent events route.&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;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/ticker/:symbol/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To enable &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt;, we need to flush a couple of headers back to our client. We want the &lt;code&gt;Connection&lt;/code&gt; set to &lt;code&gt;keep-alive&lt;/code&gt;, &lt;code&gt;Cache-Control&lt;/code&gt; set to &lt;code&gt;no-cache&lt;/code&gt; and &lt;code&gt;Content-Type&lt;/code&gt; set to &lt;code&gt;text/event-stream&lt;/code&gt; so our client would understand that this is an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; route.&lt;/p&gt;

&lt;p&gt;In addition, I have added &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; to for CORS and &lt;code&gt;X-Accel-Buffering&lt;/code&gt; set to &lt;code&gt;no&lt;/code&gt; to avoid Nginx from messing with this route. Finally, we can flush the headers back to our client to kickstart the event stream.&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;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/ticker/:symbol/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;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="o"&gt;=&amp;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="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&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;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&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;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Accel-Buffering&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;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flushHeaders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now start streaming data by writing something into our response. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; provides a text-based protocol that we can use to help our clients differentiate between the event types. Each one of our events should look like the following:&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make our lives a bit easier, I have created a helper function to take care of serialization for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**javascript
 * SSE message serializer
 * @param {string} event: Event name
 * @param {Object} data: Event data
 * @returns {string}
 */
const EventSerializer = (event, data) =&amp;gt; {
    const jsonString = JSON.stringify(data);
    return `event: ${event}\ndata: ${jsonString}\n\n`;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On our website, we have half a dozen subjects similar to what we have created so far. To be able to differentiate between these events, we have to assign an event name to each. Let's use &lt;code&gt;price_update&lt;/code&gt; for the ticker subject. In addition, we need to filter these events based on the dynamic path that our client has subscribed. For example, on &lt;code&gt;/ticker/DOGE/event&lt;/code&gt;, we only want events related to Dogecoin. To implement these two features, let's create a new wrapper around our ticker subject to filter the pipeline and add our event name to the events.&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;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&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="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * Event stream for ticker price update
 * @param {string} symbol: ticker symbol
 * @returns {Observable&amp;lt;{data: *, name: string}&amp;gt;}
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;EventTickerStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;TickerSubject&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ticker&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;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ticker&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ticker&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="s1"&gt;price_update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="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;All left to do is merge these events into a single pipeline and create a new subscriber to write them into the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; connection. We can take use the &lt;code&gt;of&lt;/code&gt; operator to create a pipeline from all of our subjects. Then, we use the &lt;code&gt;mergeAll&lt;/code&gt; operator to collect and merge all of our observables into a single observable. Then, we can subscribe to the observable, serialize our data and write it to our response. Finally, we have to make sure to unsubscribe from our observer when the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; connection is closed. Putting all of these together, we should have something like the following&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="k"&gt;of&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="s1"&gt;rxjs&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;mergeAll&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="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/ticker/:symbol/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;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="o"&gt;=&amp;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="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&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;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&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;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Accel-Buffering&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;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flushHeaders&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;symbol&lt;/span&gt; &lt;span class="o"&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&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;stream$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;EventTickerStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;// other events ...&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;mergeAll&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;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="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="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EventSerializer&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;name&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;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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;stream$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&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;Aaannddd… that's it! We are done with our backend server. &lt;br&gt;
Here's an overall view of what we have created so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2or1hd6yaxk8088v42ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2or1hd6yaxk8088v42ok.png" alt="Alt Text" width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Server-Sent Events Client
&lt;/h3&gt;

&lt;p&gt;To subscribe to our &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; route, we can create a new instance of the EventSource interface and pass our endpoint to the constructor. Once we have an instance, we can add event handlers for specific event names to process the incoming data. In our case, we can subscribe to the &lt;code&gt;price_update&lt;/code&gt; event for Dogecoin and use the data to update our UI.&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;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/ticker/DOGE/event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price_update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="p"&gt;{&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="c1"&gt;// use the data to update the UI&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// close the connection when needed&lt;/span&gt;
&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the end of the day, I am pleased about this architecture as it satisfies most of my requirements for this project. Going with a reactive design allowed me to implement many complex features more efficiently and less error-prone than an imperative model. Higher-level functions provided by &lt;a href="https://rxjs-dev.firebaseapp.com/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt;, such as throttleTime and bufferTime solved many of my problems very quickly and saved me a lot of development time. Completing the first iteration of the MVP took us about 4 days.&lt;/p&gt;

&lt;p&gt;I also wanted to deploy our services on the smallest virtual machine to reduce costs and benchmark server-side performance. Thus, I went with the $5/month digital ocean droplet. Over the last week, our server has served over 3.7M requests and over 120M events, and at one point, we had over 500 concurrent clients, which I think is a pretty damn good benchmark.&lt;/p&gt;

&lt;p&gt;In terms of scalability, we still have a lot of room to grow vertically and increase the resources available on the VM. But if we want to grow horizontally, the current architecture allows us to deploy proxies that subscribe to our pipelines, either through our &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;SSE&lt;/a&gt; endpoint or to the subjects over the network, and then multiplex the events to more clients.&lt;/p&gt;

&lt;p&gt;That concludes the discussion on how I implemented an event-driven server for our project. You can check out the final result at &lt;a href="https://monke.cafe" rel="noopener noreferrer"&gt;Monke Cafe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading; if you would like to chat, you can find me on Twitter &lt;a href="https://twitter.com/ImSh4yy" rel="noopener noreferrer"&gt;@imsh4yy&lt;/a&gt; or via responses here.&lt;/p&gt;

&lt;p&gt;Update: I have recently started working on a new project and have been using the same architecture design for pushing down information to my users. I would love to hear your feedback on the project: &lt;a href="https://checkridehq.com" rel="noopener noreferrer"&gt;checkridehq.com&lt;/a&gt;, &lt;a href="https://logsnag.com" rel="noopener noreferrer"&gt;LogSnag - Track your projects' events&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>redis</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
