<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Elie</title>
    <description>The latest articles on DEV Community by Elie (@elie222).</description>
    <link>https://dev.to/elie222</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%2F134867%2Fc610cad7-53bc-458d-a697-f67fa3085942.jpeg</url>
      <title>DEV Community: Elie</title>
      <link>https://dev.to/elie222</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elie222"/>
    <language>en</language>
    <item>
      <title>Inside the Open-Source Novu Notification Engine</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Thu, 14 Mar 2024 15:03:00 +0000</pubDate>
      <link>https://dev.to/elie222/inside-the-open-source-novu-notification-engine-311g</link>
      <guid>https://dev.to/elie222/inside-the-open-source-novu-notification-engine-311g</guid>
      <description>&lt;p&gt;In this post we'll dive into what Novu is, why you'd want to use it, and then jump into the code behind it.&lt;/p&gt;

&lt;p&gt;You don't need to know how it works to use it, but it will help you understand it at a deeper level, and will teach you how to use Redis BullMQ on your own projects.&lt;/p&gt;

&lt;p&gt;If you prefer video, here's my video on the same topic:&lt;/p&gt;

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

&lt;p&gt;Novu, is open-source notification infrastructure. It simplifies the process of integrating notifications into your app, supporting various channels like email, SMS, and push notifications. Novu has 31,000 stars on GitHub!&lt;/p&gt;

&lt;p&gt;Before we begin, I also run a &lt;a href="https://youtube.com/elie2222" rel="noopener noreferrer"&gt;YouTube channel on open source&lt;/a&gt;, and am building my an open source email app called &lt;a href="https://www.getinboxzero.com" rel="noopener noreferrer"&gt;Inbox Zero&lt;/a&gt; that helps people clean up and automate their inbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Why Novu?
&lt;/h2&gt;

&lt;p&gt;Why use Novu? Why not send the email notifications to your users yourself using something like SES or Resend?&lt;/p&gt;

&lt;p&gt;As your app grows, notifications become more complex.&lt;/p&gt;

&lt;p&gt;If you're building an app like Twitter and you want to notify users when they receive messages, you wouldn't want to bombard the most popular user with individual messages every few seconds.&lt;/p&gt;

&lt;p&gt;Novu offers a smarter solution. Instead of sending out notifications immediately it does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aggregate notifications: group notifications together based on a specific timeframe (e.g., 30 minutes). This prevents users from being overwhelmed by a constant stream of individual messages.&lt;/li&gt;
&lt;li&gt;Optimize delivery: choose the most appropriate channel for each notification (email, push notification, etc.), ensuring users receive them in a way they prefer.&lt;/li&gt;
&lt;li&gt;Scale efficiently: Novu is built to handle high volumes of notifications without compromising performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;p&gt;To integrate Novu into your project, you have to utilize their API, for example using Novu node package:&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;Novu&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;@novu/node&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;novu&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;Novu&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;NOVU_API_KEY&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;novu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;WORKFLOW_TRIGGER_ID&amp;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;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;subscriberId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;UNIQUE_SUBSCRIBER_IDENTIFIER&amp;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;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john@doemail.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;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Doe&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;payload&lt;/span&gt;&lt;span class="p"&gt;:&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;Hello World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://happycorp.com/logo.png&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;You trigger a notification by providing the workflow ID, along with an object containing the subscriber ID for who you want to send this message to, and the payload, which represents the message to be send to the user.&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%2Ff7ph08k3s6al865tomp6.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%2Ff7ph08k3s6al865tomp6.png" alt="Novu React component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've triggered the notification, you can easily display it in your project's UI. Novu provides packages for UI, as shown in the image above, although you have the option to build your own. This makes it simpler to integrate notifications directly into your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Providers
&lt;/h2&gt;

&lt;p&gt;There are a lot of different providers you can connect to. For example, if you're sending email, you could use Sendgrid, Mailgun, and SES. You could send messages via SMS using Twilio. For more details, you can check the list of Novu Providers here.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Glimpse into Novu Cloud
&lt;/h2&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%2Fm3609bvojcr6nm5we1b0.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%2Fm3609bvojcr6nm5we1b0.png" alt="Novu cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what Novu cloud-hosted offering looks like. Let's set up a new workflow for a mention in a comment.&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%2Fr43xspdxc06jiighp865.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%2Fr43xspdxc06jiighp865.png" alt="Novu steps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step is to call the trigger, after calling the trigger the user gets an in-app notification immediately. This notification tells the user that &lt;code&gt;commenterName&lt;/code&gt; mentioned them in &lt;code&gt;commentSnippet&lt;/code&gt; which are variables that are passed in the payload when the trigger is called.&lt;/p&gt;

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

{{commenterName}} has mentioned you in &amp;lt;b&amp;gt; "{{commentSnippet}}" &amp;lt;/b&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The third step is creating a digest. A digest gathers notifications to use them later on. In this template, we will wait for 30 minutes to gather all received notifications.&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%2Fdvg6owi4tknahwx0npu4.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%2Fdvg6owi4tknahwx0npu4.png" alt="Novu send email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is sending an email. The email content varies depending on the number of mentions received.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;

&lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;digest&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mentionedUser&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt; and &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;total_count&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt; others mentioned you in a comment.
&lt;span class="k"&gt;{{else}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;mentionedUser&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt; mentioned you in a comment.
&lt;span class="k"&gt;{{/if}}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The Power Behind Novu
&lt;/h2&gt;

&lt;p&gt;Novu uses NestJS a framework within NodeJS, which brings a solid structure to it. It uses MongoDB and Redis as its databases.&lt;/p&gt;

&lt;p&gt;Novu serves major clients, operating at a large scale and handling hundreds of millions of events monthly. It's built for scalability, utilizing Kubernetes and AWS auto-scaling for the management, enabling it to scale horizontally.&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction to BullMQ: A Core Component
&lt;/h2&gt;

&lt;p&gt;A core piece of Novu's back-end is BullMQ, which is used as a message broker. It's a queue where you add tasks to it and there are workers that listens to jobs.&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;Queue&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;bullmq&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;myQueue&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;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addJobs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;myQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myJobNAme&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;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;myQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myJob 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="na"&gt;qux&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&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;await&lt;/span&gt; &lt;span class="nf"&gt;addJobs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You might want to use queues if you have long running operations. Imagine an API that compresses images. Each image takes one minute to compres. You don't want your API to take a minute to respond.&lt;/p&gt;

&lt;p&gt;The solution?&lt;/p&gt;

&lt;p&gt;Asynchronous processing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You wait for someone to call your API to compress an image.&lt;/li&gt;
&lt;li&gt;Immediately return a success response.&lt;/li&gt;
&lt;li&gt;In the background, the job is added the to a queue.&lt;/li&gt;
&lt;li&gt;The workers are then going to do the image compression.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This also makes the system highly scalable. You can have many background workers handling the compression and can scale out these workers separately from the rest of your app.&lt;/p&gt;

&lt;p&gt;Here, you can see a worker being set up, which is listening for events.&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;Worker&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;bullmq&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;worker&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;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&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="nx"&gt;job&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;// Will print { foo: 'bar'} for the first job&lt;/span&gt;
  &lt;span class="c1"&gt;// and { qux: 'baz' } for the second.&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can have a hundred different workers working in parallel and they're all managing different items in the queue. And this is a great way to scale your app and tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive into Novu's Inner Workings
&lt;/h2&gt;

&lt;p&gt;Every notification begins with a trigger. To understand this process, let's delve into the backend. Navigate to the &lt;code&gt;events&lt;/code&gt; folder within Novu's repository, and open the events controller file. Here, you'll find an endpoint dedicated to handling POST requests, labeled &lt;code&gt;/trigger&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the trigger is initiated, the function &lt;code&gt;trackEvent&lt;/code&gt; serves as a handler for incoming trigger events, it takes two parameters &lt;code&gt;UserSession&lt;/code&gt; and &lt;code&gt;Body&lt;/code&gt;. Inside the function a method is called to process the received data.&lt;/p&gt;

&lt;p&gt;This method creates a command object with various details extracted from the user's session and the trigger event, once the command is ready, it's executed, and returns the result as a &lt;code&gt;TriggerEventResponseDto&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/apps/api/src/app/events/events.controller.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/trigger&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="nd"&gt;ApiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TriggerEventResponseDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;UserSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IJwtPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Body&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="nx"&gt;TriggerEventRequestDto&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TriggerEventResponseDto&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseEventRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;ParseEventRequestMulticastCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// parameters&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;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TriggerEventResponseDto&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;Inside the &lt;code&gt;parseEventRequest&lt;/code&gt;, this class is doing all sorts of validation, making sure there are workflow steps and active workflow steps. Upon successful validation, this is the point where we actually handle the request and get the job data and add it to a workflow queue service.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/apps/api/src/app/events/usecases/parse-event-request/parse-event request.usecase.ts&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modifyAttachments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageHelperService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadAttachments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&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;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;attachment&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;attachment&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;defaultPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verifyPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;VerifyPayloadCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;template&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;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;defaultPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&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;jobData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IWorkflowDataDto&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="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workflowQueueService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="nx"&gt;transactionId&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;jobData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organizationId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Inside the workflow queue service. All it does is add something to the queue or if we get an array of jobs it will bulk add them to the queue. As mentioned earlier, this is how the BullMQ is implemented. When you call the Novu API to trigger a notification, it's adding a notification to the BullMQ queue.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/packages/application-generic/src/services/queues/workflow-queue.service.ts&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IWorkflowJobDto&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;addBulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IWorkflowBulkJobDto&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addBulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After submitting events to the queue, the next step involves running a worker to process those events on the receiving end. &lt;code&gt;createWorker&lt;/code&gt; constructs a BullMQ worker, specifying how to process tasks from a queue by providing the processing function, the topic name, and worker options. &lt;code&gt;initWorker&lt;/code&gt; simplifies worker setup, logging initialization and calling &lt;code&gt;createWorker&lt;/code&gt; to start processing tasks efficiently.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/packages/application-generic/src/services/workers/worker-base.services.ts&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;initWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WorkerProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;WorkerOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Worker &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; initialized`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LOG_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;createWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WorkerProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WorkerOptions&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If I jump back to standard worker, you'll see that I have this.initWorker and over here &lt;code&gt;getWorkerProcessor()&lt;/code&gt; is where I'm returning the function.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/apps/worker/src/app/workflow/services/standard.worker.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandardWorker&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;StandardWorkerService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;runJob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RunJob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// constructor parameters&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&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;BullMqService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflowInMemoryProviderService&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWorkerProcessor&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWorkerOptions&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worker&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;failed&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;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IStandardDataDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobHasFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&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;private&lt;/span&gt; &lt;span class="nf"&gt;getWorkerOptions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;WorkerOptions&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="nf"&gt;getStandardWorkerOptions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;backoffStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBackoffStrategies&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;Within the &lt;code&gt;getWorkerProcess&lt;/code&gt; definition, you'll see that it's calling &lt;code&gt;runJob&lt;/code&gt; to execute while also parsing the &lt;code&gt;minimalJobData&lt;/code&gt; that we've received.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/apps/worker/src/app/workflow/services/standard.worker.ts&lt;/span&gt;
&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PinoLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&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;_this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runJob&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RunJobCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minimalJobData&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`Failed to run the job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;minimalJobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; during worker processing`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;LOG_CONTEXT&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&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;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;If you look inside the &lt;code&gt;runJob&lt;/code&gt;, you'll see that it's calling &lt;code&gt;sendMessage.execute&lt;/code&gt;. This command directs the &lt;code&gt;sendMessage&lt;/code&gt; class to dispatch various messages, which can be sent via SMS or email.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/apps/worker/src/app/workflow/usecases/run-job/run-job.usecase.ts&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;SendMessageCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// parameters&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 jump to the &lt;code&gt;sendMessageEmail&lt;/code&gt; class and check the important part, which is the &lt;code&gt;sendMessage&lt;/code&gt;. Inside this, it creates a &lt;code&gt;mailFactory&lt;/code&gt; and gets the handler, and what this is doing is getting the provider that we need.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// novu/apps/worker/src/app/workflow/usecases/send-message/send-message email.usecase.ts&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;integration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IntegrationEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;mailData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IEmailOptions&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;MessageEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendMessageCommand&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;mailFactory&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;MailFactory&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;mailHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildFactoryIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;mailData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...rest of the code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;mail.factory.ts&lt;/code&gt;, you'll see different provider handlers. If we jump into one of the providers, specifically the Mailgun provider, you can see all it does is call the API that actually sends an email via Mailgun. Once Mailgun successfully delivers the message to the user's inbox, the notification sending process ends.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;// novu/providers/mailgun/src/lib/mailgun.provider.ts&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailgunClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailgun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&lt;a href="https://api.mailgun.net" rel="noopener noreferrer"&gt;https://api.mailgun.net&lt;/a&gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;});&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Final Thoughts&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;This exploration has scratched the surface of Novu's capabilities. While there's more to discover, you now grasp the essential flow: triggering events (via UI or API) creates DTOs, pushes them to a queue, and triggers workers to process and send notifications (SMS, email, etc.).&lt;/p&gt;

&lt;p&gt;Remember, this is just a glimpse into Novu's broad range of functionalities. Hopefully, you've gained a solid understanding of the core workflow. If you're interested in learning more, I highly recommend exploring the project's &lt;a href="https://github.com/novuhq/novu" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay in touch
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow me on &lt;a href="https://twitter.com/elie2222" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Subscribe to my &lt;a href="https://youtube.com/elie2222" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Join my &lt;a href="https://opensource.beehiiv.com/" rel="noopener noreferrer"&gt;Learn from Open Source newsletter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Star Inbox Zero on &lt;a href="https://www.getinboxzero.com/github" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>redis</category>
      <category>bullmq</category>
    </item>
    <item>
      <title>AI assistant for managing your email</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Wed, 06 Mar 2024 19:10:00 +0000</pubDate>
      <link>https://dev.to/elie222/ai-assistant-for-managing-your-email-4kjj</link>
      <guid>https://dev.to/elie222/ai-assistant-for-managing-your-email-4kjj</guid>
      <description>&lt;p&gt;Inbox Zero is the best way to create an AI assistant to manage your email for you automatically.&lt;/p&gt;

&lt;p&gt;You give it rules in plain English.&lt;/p&gt;

&lt;p&gt;You can run it on autopilot or manually approve each action the AI suggests for you.&lt;/p&gt;

&lt;p&gt;It's truly magical.&lt;/p&gt;

&lt;p&gt;I highly recommend checking it out.&lt;/p&gt;

&lt;p&gt;The app is fully open source too!&lt;/p&gt;

&lt;p&gt;The site: &lt;a href="https://getinboxzero.com"&gt;https://getinboxzero.com&lt;/a&gt;&lt;br&gt;
The GitHub: &lt;a href="https://getinboxzero.com/twitter"&gt;https://getinboxzero.com/github&lt;/a&gt;&lt;br&gt;
Original post: &lt;a href="https://telegra.ph/AI-assistant-for-managing-your-email-03-06"&gt;https://telegra.ph/AI-assistant-for-managing-your-email-03-06&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The AI magic behind open v0 ✨ - an open source React Tailwind generator</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Tue, 26 Sep 2023 20:17:54 +0000</pubDate>
      <link>https://dev.to/elie222/the-ai-magic-behind-open-v0-an-open-source-react-tailwind-generator-3096</link>
      <guid>https://dev.to/elie222/the-ai-magic-behind-open-v0-an-open-source-react-tailwind-generator-3096</guid>
      <description>&lt;p&gt;Last week Vercel announced v0 💎&lt;/p&gt;

&lt;p&gt;It gave us a glimpse into the future of UI dev 👨‍💻&lt;/p&gt;

&lt;p&gt;A few days later an open source alternative called openv0 was dropped 📖&lt;/p&gt;

&lt;p&gt;In this week's video I jump into the AI magic behind it ✨&lt;/p&gt;

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

</description>
      <category>openai</category>
      <category>tailwindcss</category>
      <category>react</category>
      <category>v0</category>
    </item>
    <item>
      <title>The Code Behind OpenStatus and how it uses Turborepo</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Sun, 03 Sep 2023 18:39:00 +0000</pubDate>
      <link>https://dev.to/elie222/the-code-behind-openstatus-and-how-it-uses-turborepo-934</link>
      <guid>https://dev.to/elie222/the-code-behind-openstatus-and-how-it-uses-turborepo-934</guid>
      <description>&lt;p&gt;OpenStatus is an open source monitoring service! It’s built with Next.js, Turso, Tinybird, Turborepo, Shadcn UI, and Tailwind!&lt;/p&gt;

&lt;p&gt;In this video I cover the code behind OpenStatus and how to use Turborepo to build a monorepo Next.js app.&lt;/p&gt;

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

&lt;p&gt;To learn from more open source projects, subscribe to my YouTube channel: &lt;a href="https://youtube.com/elie2222"&gt;https://youtube.com/elie2222&lt;/a&gt; &lt;/p&gt;

</description>
      <category>opensource</category>
      <category>turbo</category>
    </item>
    <item>
      <title>Best Admin solutions for Node.js apps.</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Mon, 18 Jan 2021 21:49:19 +0000</pubDate>
      <link>https://dev.to/elie222/best-admin-solutions-for-node-js-apps-4oel</link>
      <guid>https://dev.to/elie222/best-admin-solutions-for-node-js-apps-4oel</guid>
      <description>&lt;p&gt;Have you ever wasted time on building a custom admin?&lt;/p&gt;

&lt;p&gt;There are a lot of out the box solutions can speed things up for you. Some worth looking at are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forest Admin&lt;/li&gt;
&lt;li&gt;Retool&lt;/li&gt;
&lt;li&gt;React Admin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Forest Admin will generate an admin for you in under 5 minutes. It's not great if you need a lot of customisation although it can be customised to what you need most of the time.&lt;/p&gt;

&lt;p&gt;Retool is great too. You can write any database queries you need to grab data direct and display it in Retool components.&lt;/p&gt;

&lt;p&gt;The last tool is React Admin. Here you actually write frontend code but many of the components and frontend logic has been written for you.&lt;/p&gt;

&lt;p&gt;Special mention to Airtable which can be used with care in specific cases. Be careful not to duplicate a lot of information from your real database. If things get out of sync it can be annoying to know what's accurate. I'd recommend not duplicating any data.&lt;/p&gt;

&lt;p&gt;Final note: any third part service will help you get going quickly. But if you need a lot of customisation it may be worth to build your admin from scratch. Note that most of these solutions are for internal use, and not for client facing admin views.&lt;/p&gt;

&lt;p&gt;Lastly, if you do choose to use a third party admin tool you can always build custom tools alongside it. You're not stuck with any specific tool, and can even use many of them in conjunction. There's no reason Retool and Forest Admin can't run side by side and you use whichever is easier for you at that moment.&lt;/p&gt;

&lt;p&gt;Want to learn more? Here's the full video:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/a4vXsrM67Rs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Link:&lt;br&gt;
&lt;a href="https://youtu.be/a4vXsrM67Rs"&gt;https://youtu.be/a4vXsrM67Rs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>admin</category>
      <category>programming</category>
    </item>
    <item>
      <title>How I Built This: IsraelVC — Gatsby, Google Sheets, Now Serverless</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Wed, 29 May 2019 23:06:26 +0000</pubDate>
      <link>https://dev.to/elie222/how-i-built-this-israelvc-gatsby-google-sheets-now-serverless-4b56</link>
      <guid>https://dev.to/elie222/how-i-built-this-israelvc-gatsby-google-sheets-now-serverless-4b56</guid>
      <description>&lt;p&gt;I recently launched a mini side project, &lt;a href="https://israelvc.co/"&gt;IsraelVC.co&lt;/a&gt;, that catalogues the latest investments in Israeli high tech.&lt;/p&gt;

&lt;p&gt;It’s a simple site: one page containing a list of the 20 most recent investments that have happened in Israel. It also has a Google Sheet connected to it which acts as the database for the site. The database contains 4000 investments. In the near future the site will show all the investments too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2y-DgtFj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2400/1%2A-AC79hu0Zlrsv7y-hVj_HA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2y-DgtFj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2400/1%2A-AC79hu0Zlrsv7y-hVj_HA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is about how I built it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gatsby (React)&lt;/li&gt;
&lt;li&gt;Google Sheets&lt;/li&gt;
&lt;li&gt;Now Serverless&lt;/li&gt;
&lt;li&gt;Netlify&lt;/li&gt;
&lt;li&gt;Mailchimp&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gatsby
&lt;/h3&gt;

&lt;p&gt;Gatsby is a React based static site generator. It allows you to easily create sites that are lightning fast and SEO friendly.&lt;/p&gt;

&lt;p&gt;Hosting a Gatsby site is very simple. You can use a service such as Netlify or Now to host the site for free. Free doesn’t mean these services are bad. These services are the best available. They will serve your content from a CDN located near your users. You can’t ask for much better speed and is infinitely scalable without needing to maintain any servers. Win. Win. Win.&lt;/p&gt;

&lt;p&gt;Netlify and Now both have continuous deployment set up. What this means is that you can push to your GitHub repo and your site will automatically updated. If you update and push a commit on a development branch, a new deploy will be published at its own url for that branch. There is even a published site for every commit. It’s a great all round developer experience. If you use Now you can also deploy with their command line tool, by simply typing &lt;code&gt;now&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Sheets
&lt;/h3&gt;

&lt;p&gt;I use Google Sheets as the database for the project. A typical site would use PostgreSQL, MongoDB or some other database. This project was small enough that I could rely on Sheets to do the job.&lt;/p&gt;

&lt;p&gt;Using Sheets as a database may come as a surprise to some people. That’s not how you develop I hear you shout!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So why use Sheets?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For one it’s super simple to use. Anyone can use it. To add information you just open it up and start typing. Compare that to a traditional database. You have to be a developer to update it or read information in it.&lt;/p&gt;

&lt;p&gt;Sheets provides permission based read/write access out the box. No need to build an admin panel.&lt;/p&gt;

&lt;p&gt;You don’t have to worry about server maintenance or scaling. That’s Google’s job. If I had to list millions of investments I may have run into issues, but that wasn’t a case I had to deal with.&lt;/p&gt;

&lt;p&gt;In this specific case I also wanted to give users easy access to the data so that they could play with it as they wish. The spreadsheet itself is public.&lt;br&gt;
Sheets has a tonne of functionality built in. For example, being able to sort or search through thousands of investments. This saved me the trouble of building out the functionality myself.&lt;/p&gt;

&lt;p&gt;One other thing you can do is connect a Google Forms to Sheets, with the form responses going straight into the spreadsheet.&lt;/p&gt;

&lt;p&gt;Traditional databases have their benefits. Most web or mobile apps need a&lt;br&gt;
backend with a database. Performance, large scale, schemas, backups, triggers, joins, fine grained user access control. The list goes on. You could probably figure out a way to implement some of these features using a spreadsheet, but ultimately this probably isn’t a good idea. Luckily, IsraelVC didn’t need these benefits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternatives&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are some alternatives to Sheets. One is simply storing the information in the repo as JSON. Another option is storing Markdown files in the repo containing the data. This is how a typical Gatsby blog is built. For example, &lt;a href="https://overreacted.io/"&gt;https://overreacted.io/&lt;/a&gt; by Dan Abramov, but there’s an endless list of tech blogs just like it.&lt;/p&gt;

&lt;p&gt;The first iteration of IsraelVC actually did this. I used Netlify CMS to help. It gives you a simple admin interface in which you can update your blog posts (or any other data on the site). Once you hit “Publish” it commits the changes to your git repo and if you’re using a CD tool will update your site automatically.&lt;/p&gt;

&lt;p&gt;For a blog or landing page where you want to update data easily, Netlify CMS is a great option. You can write straight to the repo and for many developers this is a good solution, but if you want to allow less technical members of your team to update items without needing your help, a CMS is a better solution.&lt;/p&gt;

&lt;p&gt;Netlify CMS has its downsides. It’s a little slow to publish and update posts. For the occasional blog post this doesn’t matter, but if you’re entering a lot of data often this can be annoying.&lt;/p&gt;

&lt;p&gt;Other headless CMS systems include Contentful, Prismic, and GraphCMS. Wordpress also offers a headless CMS system. I haven’t used any of these so I won’t comment on them, but I assume they provide a more expansive solution than Netlify CMS and will be better a choice for many teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Now Serverless
&lt;/h3&gt;

&lt;p&gt;I use Now as the API for my site. All the API does is query my Google sheet of investments and return it.&lt;/p&gt;

&lt;p&gt;You can see the code for it on GitHub here:&lt;/p&gt;

&lt;p&gt;All in all it’s 60 lines of code and you can deploy it with a single command:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;now
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It’s also possible to set up continuous deployment for Now with a few clicks, but I have yet to do that.&lt;/p&gt;

&lt;p&gt;There are a few pieces of magic I’d like to talk about here.&lt;/p&gt;

&lt;p&gt;I don’t have to maintain a server, it’s infinitely scalable, and free (well I pay a minimal amount for it, but you can go with the free plan too and get the same functionality).&lt;/p&gt;

&lt;p&gt;It’s fast. Now recently launched &lt;a href="https://zeit.co/blog/serverless-pre-rendering"&gt;serverless pre-rendering&lt;br&gt;
(SPR)&lt;/a&gt;. What it does is cache responses from your serverless function. My function makes a call to Google Sheets API which can take a few seconds to respond. I don’t want my users waiting a few seconds for a response. They have more important things to do like investing in startups.&lt;/p&gt;

&lt;p&gt;SPR caches the response at the edge on CDN and responds immediately. In the background, the function grabs the latest sheets data or whatever else it’s doing and updates the CDN for the next users that visit the site.&lt;/p&gt;

&lt;p&gt;To enable SPR, you have to add a single line of code to your function:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;res.setHeader(‘Cache-Control’, ‘s-maxage=30, stale-while-revalidate=3600, max-age=0’)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;s-maxage=30&lt;/code&gt;— tells the CDN to cache the response for 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stale-while-revalidate=3600&lt;/code&gt; — tells the CDN that if the data is stale (i.e. the cached data was updated more than 30 seconds ago), then the stale version of the site should be served. And the cache will be updated in the background so that the data is fresh for the next user that comes to the site. If someone hasn’t been to the site in a long time and the stale data is more than 3600 seconds old (1 hour), the CDN is instructed to not serve the stale data, but instead fetch the latest data from Sheets and serve that to the client directly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;max-age=0&lt;/code&gt;— tells the client not to cache the response. The client will always request the latest data from our API.&lt;/p&gt;

&lt;p&gt;To test the serverless function locally while developing I used &lt;code&gt;now dev&lt;/code&gt;. It’s a new tool and has its problems. It randomly crashes every so often and hot reloading can take a while. The Now team has been working on it and releasing updates. Recently it has worked better, but it’s still not the best experience.&lt;/p&gt;

&lt;p&gt;Netlify also provides serverless. It’s called Netlify Functions. I had a little more trouble setting this up. The settings needed for it in the &lt;code&gt;netlify.toml&lt;/code&gt; file are confusing, and &lt;code&gt;netlify dev&lt;/code&gt; seems even buggier than &lt;code&gt;now dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both &lt;code&gt;netlify dev&lt;/code&gt; and &lt;code&gt;now dev&lt;/code&gt; are very new, so I will cut them some slack. These are two amazing companies if I haven’t already made it clear how I feel about them :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Netlify
&lt;/h3&gt;

&lt;p&gt;I currently use Netlify for frontend hosting. I love using Netlify for static deployments. It’s so easy to use. 10/10 on that front. It’s free and even has features like automatic lossless image compression that Now is missing. Having CD setup out the box is a joy to use, especially when developing a project on a team.&lt;/p&gt;

&lt;p&gt;There is a downside to having the frontend hosted on Netlify and the serverless backend on Now. In the future I will likely move the frontend to Now as well. The main reason is that I’d like to avoid an extra DNS lookup which happens when the API and frontend are at different domains. This is a bit of a micro optimisation that isn’t strictly necessary, but having seen the DNS lookup for the API take 2.5 seconds one time I’d like to cut that out. Another way to solve the problem is to move the backend to Netlify Functions, but I prefer Now Serverless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After posting this the team at Gatsby pointed out another way of integrating Gatsby with Sheets without the need for the Now API server.&lt;/p&gt;

&lt;p&gt;There is a Gatsby Sheets plugin:&lt;br&gt;
&lt;a href="https://www.gatsbyjs.org/packages/gatsby-source-google-sheets/"&gt;https://www.gatsbyjs.org/packages/gatsby-source-google-sheets/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This plugin grabs the data from your spreadsheet at build time. You use Gatsby graphql queries to grab the data you want from the sheet.&lt;/p&gt;

&lt;p&gt;Taking this approach, you need to trigger a rebuild of your site whenever the Sheets data changes. You can do this by going to your Netlify admin, using the Netlify API, or by adding a button to the sheet that calls the Netlify API and rebuilds the site on click. This third approach is cool and documented &lt;a href="https://jmolivas.weknowinc.com/how-use-google-spreadsheet-manage-content-and-trigger-deployment-your-gatsbyjs-site?source=post_page-----714ed5023bd3----------------------"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mailchimp
&lt;/h3&gt;

&lt;p&gt;Not much to write here. I will be sending out a weekly email newsletter with that week’s investments so I added a simple Mailchimp form to the site. It’s free up to 2000 users and no hassle. For now I will be manually sending the emails. In the future I may automate this process and use a solution such as Mailgun or SendGrid to do the sending.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Some great tools exist to massively speed up development, minimise maintenance and lower costs.&lt;/p&gt;

&lt;p&gt;Handling servers, scaling, and performance can all be headaches. Paying a few dollars per month for every mini side project you launch is also annoying. Nowadays you can use the best of the best for free.&lt;/p&gt;

&lt;p&gt;The stack I covered is simple, but extremely powerful. As a freelance developer I deal with databases and deployments on a weekly basis. I also maintain a site with hundreds of thousands of users. I didn’t choose to use this stack for lack of other options. I chose it because it’s an awesome solution. Deploying to Now or Netlify feels like cheating, but it’s not. It’s the smart choice in many cases.&lt;/p&gt;

&lt;p&gt;I’d love to hear your thoughts or any questions in the comments below.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post and would like to read more like it in the future, be sure to smash the follow button below. Feel free to reach out to me on Twitter &lt;a href="https://twitter.com/elie2222"&gt;@elie2222&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;Or at my website: &lt;a href="https://elie.tech/"&gt;elie.tech&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/tag/netlify?source=post"&gt;Netlify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/tag/now?source=post"&gt;Now&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/tag/serverless?source=post"&gt;Serverless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/tag/startup?source=post"&gt;Startup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/tag/gatsbyjs?source=post"&gt;Gatsbyjs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gatsby</category>
      <category>serverless</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>4 Techniques for Fixing NPM Packages</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Tue, 05 Mar 2019 10:05:46 +0000</pubDate>
      <link>https://dev.to/elie222/techniques-for-fixing-npm-packages-4ko6</link>
      <guid>https://dev.to/elie222/techniques-for-fixing-npm-packages-4ko6</guid>
      <description>&lt;p&gt;If you’re a Javascript you likely make use of many npm packages. There are times a package has a bug or doesn’t do exactly what you want. This post details a few different ways to work around this.&lt;/p&gt;

&lt;p&gt;I have often found myself wasting hours trying to make a one line fix, so I hope the strategies listed here will be of help to others. Many of the tools mentioned are also useful when developing a package.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;p&gt;We’ll start with the obvious. The most basic thing you can do to solve your problems is to use a different package. If it’s a small piece of functionality, you can rewrite it yourself and avoid having to use the package.&lt;/p&gt;

&lt;p&gt;This sometimes works, but if the package contains a lot of code this solution may not be practical. Beyond completely rewriting from scratch, you could copy and paste the contents of the package into your local project and edit as you need. The disadvantage of such an approach is that you won’t be able to easily follow future updates made by others to the package. Likely not the best solution in many instances.&lt;/p&gt;

&lt;p&gt;Perhaps the first thing you should do before trying to fix a package yourself is post an issue in the GitHub repo. The functionality you are trying to add may already exist, or you are misusing the library and the package maintainers can point you in the right direction. If it really is a bug the package maintainers are likely the ones best suited to fix it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix Locally
&lt;/h3&gt;

&lt;h4&gt;
  
  
  npm/yarn link
&lt;/h4&gt;

&lt;p&gt;The first way to fix a package is to fork and clone it locally. You can then use &lt;code&gt;npm link&lt;/code&gt; or &lt;code&gt;yarn link&lt;/code&gt; to use it in your project. Any changes you make to the forked package will be reflected in your project.&lt;/p&gt;

&lt;p&gt;You need to run two commands to link. &lt;a href="https://yarnpkg.com/lang/en/docs/cli/link/" rel="noopener noreferrer"&gt;Using yarn&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn link # in the root of the forked package
yarn link forked-package # in the root of your project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will also need to run &lt;code&gt;yarn install&lt;/code&gt; on the forked package and run any build/prepublish steps. In the past I have sometimes found the build/prepublish step to be annoying to get working. Your mileage may vary and it depends on how the package was written.&lt;/p&gt;

&lt;p&gt;Another problem with this solution is that it only creates a local symlink to the package. This means that you’ve only solved the problem locally, but not for team members.&lt;/p&gt;

&lt;p&gt;Once you’ve fixed the problem locally, you will want to publish it to GitHub and either rely on your forked version of the package, make a pull request to the main package, or publish the package to npm under your name.&lt;/p&gt;

&lt;h4&gt;
  
  
  yalc
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/whitecolor/yalc" rel="noopener noreferrer"&gt;Yalc&lt;/a&gt; is similar to npm link, but it publishes the forked package locally rather than creating a symlink. I have found that it often works better than npm link as it handles the full build process a package maintainer uses to publish to npm including &lt;code&gt;npm pack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://github.com/whitecolor/yalc" rel="noopener noreferrer"&gt;yalc&lt;/a&gt; is similar to using npm/yarn link:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yalc publish # in the root of the forked package
yalc add forked-package # in the root of your project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;One problem I’ve run into using yalc is when you want to fix a package that is depended on by another package. For more discussion on that situation see &lt;a href="https://github.com/whitecolor/yalc/issues/16" rel="noopener noreferrer"&gt;this&lt;/a&gt; issue.&lt;/p&gt;

&lt;p&gt;You can commit yalc changes to git to share the fix with other team members. This is useful to make a quick fix, although likely should not be used as a long term solution.&lt;/p&gt;

&lt;h4&gt;
  
  
  patch-package
&lt;/h4&gt;

&lt;p&gt;Another solution is to use &lt;a href="https://github.com/ds300/patch-package" rel="noopener noreferrer"&gt;patch-package&lt;/a&gt;. To make use of it you don’t need to fork the buggy package. You can simply edit your &lt;code&gt;node_modules&lt;/code&gt; directory with the changes you want.&lt;/p&gt;

&lt;p&gt;To use patch-package:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# add to your package.json:
 "scripts": {
   "postinstall": "patch-package"
 }

# fix the broken file in your project:
# e.g. node_modules/some-package/brokenFile.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can add the patches to your git repo to share the changes with the rest of the team. Patch-package applies these changes to the project itself by creating a patch file with the changes.&lt;/p&gt;

&lt;p&gt;There are some problems with patch-package however. When a package author publishes a project to npm, many of the original files are lost in the process.&lt;/p&gt;

&lt;p&gt;For example, if the project was written in TypeScript/ES6+, the original files may not have been published to npm, just the final transpiled version. This is the case if the &lt;code&gt;package.json&lt;/code&gt; contains the &lt;code&gt;files&lt;/code&gt; field, or the project contains a &lt;code&gt;.npmignore&lt;/code&gt; file. Then not all the files in the project will be downloaded to your &lt;code&gt;node_modules&lt;/code&gt; folder. Usually this is a good thing as it reduces the amount of data that needs to be downloaded, but it makes it difficult to use patch-package.&lt;/p&gt;

&lt;p&gt;You can still edit the final ES5 or minified version using patch-package, but this is usually an uncomfortable experience.&lt;/p&gt;

&lt;p&gt;Also, even if the src folder has been published to npm, you will still need to run the build steps locally. If some of the files necessary to build the project have been excluded, this will not be possible unless you grab the missing files from GitHub. At this stage it may just be easier to clone the package locally in a separate folder.&lt;/p&gt;

&lt;p&gt;Beyond this, any fixes you make with patch-package are not shared with the wider community and anyone else using this package that may benefit from your changes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Fork on GitHub
&lt;/h4&gt;

&lt;p&gt;Another solution is to make the changes you need and publish them to GitHub/GitLab (or any other source control platform you use).&lt;/p&gt;

&lt;p&gt;This can often be done in conjunction with the steps above. You may first make the fixes using &lt;em&gt;npm/yarn link&lt;/em&gt; or &lt;em&gt;yalc&lt;/em&gt;, check that all works as expected and then push the changes to your own Git repo. You can then run one of the following to add the package to your project and make it accessible to others (changing the url as appropriate):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add https://github.com/fancyapps/fancybox [remote url]

yarn add https://github.com/fancyapps/fancybox#3.0  [branch/tag]

yarn add https://github.com/fancyapps/fancybox#5cda5b529ce3fb6c167a55d42ee5a316e921d95f [commit]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to do any automated builds, you can take a look at this &lt;a href="https://stackoverflow.com/questions/48287776/automatically-build-npm-module-on-install-from-github" rel="noopener noreferrer"&gt;StackOverflow post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At this point you can also make a Pull Request to the main repo to get the changes merged in for everyone using the package. Once the changes have been merged in you can go back to using the original package.&lt;/p&gt;

&lt;p&gt;Along these lines, you can also republish the package under your own npm account and then install it as you would any npm package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Solutions
&lt;/h3&gt;

&lt;p&gt;If you are using a monorepo setup with something like Lerna or Rush, you can clone the package in question locally, and use it as you would any other Lerna package in your project.&lt;/p&gt;

&lt;p&gt;The final solution available is monkey patching. You can read more about monkey patching in Node.js &lt;a href="https://johncoder.com/monkey-patching-in-node-js/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Words
&lt;/h3&gt;

&lt;p&gt;None of the solutions above are perfect. Either patch-package or a yalc followed by committing the changes to GitHub is my favoured solution of those listed above. But all the solutions have problems. After all these years, we are still missing the holy grail for simple package fixes.&lt;/p&gt;

&lt;p&gt;If you know of some solutions that I’ve missed and know a better way to fix problems I would love to know in the comments below!&lt;/p&gt;

&lt;p&gt;If you enjoyed this article be sure to give me like and follow for similar content in the future :). You can follow me on Twitter at: &lt;a href="https://twitter.com/elie2222" rel="noopener noreferrer"&gt;elie2222&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>npm</category>
      <category>yarn</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Some Lesser Known TypeScript Features</title>
      <dc:creator>Elie</dc:creator>
      <pubDate>Fri, 15 Feb 2019 15:06:20 +0000</pubDate>
      <link>https://dev.to/elie222/some-lesser-known-typescript-features-2034</link>
      <guid>https://dev.to/elie222/some-lesser-known-typescript-features-2034</guid>
      <description>&lt;p&gt;In the past few years, TypeScript has become a popular way to write JavaScript apps. The language is immense and can do a lot of things.&lt;/p&gt;

&lt;p&gt;Here’s a short list of some rarer TypeScript features you may not be aware of to help with your development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can write numbers with underscores. For example, &lt;code&gt;1000000000&lt;/code&gt; can be written as &lt;code&gt;1_000_000_000&lt;/code&gt;. It will be compiled to the regular number in Javascript, but using this syntax makes things easier to read. The underscores can be placed anywhere in the number although writing &lt;code&gt;25&lt;/code&gt; is more readable than &lt;code&gt;2_5&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you know a variable is defined you can add a &lt;code&gt;!&lt;/code&gt; after it to access it without first checking if the value is &lt;code&gt;undefined&lt;/code&gt;. For example, you may be injecting props into a React component using a decorator (e.g. &lt;code&gt;@inject(router)&lt;/code&gt; or &lt;code&gt;@inject(store)&lt;/code&gt;). You can now write &lt;code&gt;this.props.store!.someValue&lt;/code&gt; even if the definition of store is &lt;code&gt;store?: Store&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Be careful not to overuse this feature, but there are times where it comes in handy. You can read &lt;a href="https://github.com/mobxjs/mobx-react/issues/256"&gt;here&lt;/a&gt; for further discussion of this problem. (You could always use an if statement too, but that’s more verbose for cases you know the if statement is truthy).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can set a variable to have type &lt;code&gt;never&lt;/code&gt;. This means it can’t be set to anything. How is this useful? One place it can be useful is to check you’ve handled every possibility of a switch statement. For example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warm&lt;/span&gt;&lt;span class="dl"&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;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temperature&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="s1"&gt;hot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;it's hot outside!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;it's cold outside!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above code will throw a compile-time error, because &lt;code&gt;x&lt;/code&gt; can be reached. If you add a switch case for warm with a break after it, the compile error will go away as the default block can no longer be reached.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Another lesser used type you can use is &lt;code&gt;unknown&lt;/code&gt;. This just means we have no idea what the item we have is. This may happen if we’re calling a third party API for example. If we try do &lt;code&gt;x.substr(0, 2)&lt;/code&gt; it will throw a compile error. We can use an unknown variable by first doing a type check. For example, writing &lt;code&gt;if (typeof x === “string”) x.substr(0, 2)&lt;/code&gt; would no longer throw an error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No any other useful TypeScript tricks that should be included? Feel free to mention them in the comments!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
