<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Dera Okeke's Blog]]></title><description><![CDATA[Dera Okeke's Blog]]></description><link>https://blog.dhera.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 11 May 2026 12:31:30 GMT</lastBuildDate><atom:link href="https://blog.dhera.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Dynamically Scrape Data from a Webpage with a "Load More" Button]]></title><description><![CDATA[Web scraping is a powerful tool for gathering information from websites. When web scraping in NodeJS, we can choose from an array of web NodeJS has several web scraping libraries. However, we'll use Puppeteer to handle the web scraping for this tutor...]]></description><link>https://blog.dhera.dev/how-to-dynamically-scrape-data-from-a-webpage-with-a-load-more-button</link><guid isPermaLink="true">https://blog.dhera.dev/how-to-dynamically-scrape-data-from-a-webpage-with-a-load-more-button</guid><category><![CDATA[webscraping ]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Scraping]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Wed, 26 Mar 2025 12:58:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742995840565/adf2d68a-38e4-4689-86c5-05897fad64cb.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Web scraping is a powerful tool for gathering information from websites. When web scraping in NodeJS, we can choose from an array of web NodeJS has several web scraping libraries. However, we'll use Puppeteer to handle the web scraping for this tutorial.</p>
<p>In this tutorial, we'll walk through a simple, step-by-step guide to scraping data from a website with dynamic content. We'll cover making requests using NodeJS, loading additional content, parsing information, and exporting this information to a CSV document.</p>
<p>Let's get right to it!</p>
<p><a target="_blank" href="https://github.com/chideraao/scraping-tutorial">View implementation on GitHub</a>.</p>
<h2 id="heading-step-1-prerequisites">Step 1: Prerequisites</h2>
<p>In this section, we’ll go through all the steps and installations required for this tutorial. To follow along with the rest of this guide, you will need the following:</p>
<ul>
<li><p>NodeJS: You can download and install a NodeJS version with <em>Long-Term Support (LTS)</em> from the <a target="_blank" href="https://nodejs.org/en/download/package-manager">NodeJS download page</a>. This installation adds NodeJS to your machine's directory and allows you to install dependencies with the <em>Node Package Manager (NPM)</em>.</p>
</li>
<li><p>A code editor of choice.</p>
</li>
</ul>
<p>With the environment setup completed, you can start setting up NodeJS for your project. To begin, create a new folder in a directory of choice and create a <code>scrape.js</code> file.</p>
<p>Next, you must initialize NodeJS in the newly created folder. To initialize NodeJS, run the following command at the root of the folder's directory:</p>
<pre><code class="lang-sh">npm init -y
</code></pre>
<p>This command creates a project scaffold with the <code>node_modules</code> folder and <code>package.json</code> file that contains our project's dependencies. Then, we need to download some NPM packages to scrape the website. These packages are:</p>
<ul>
<li><p><a target="_blank" href="https://www.npmjs.com/package/puppeteer">puppeteer</a>: Puppeteer is a powerful NodeJS library that automates tasks in a headless browser. It allows interaction with page elements, making it helpful in scraping dynamic content.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/json2csv">json2csv</a>: A fast and configurable JSON to CSV converter.</p>
</li>
</ul>
<p>All required packages are available on the <a target="_blank" href="https://www.npmjs.com/">NPM package registry</a>. To download them to your project, run the following command in the terminal at the root of your project's directory:</p>
<pre><code class="lang-sh">npm install puppeteer json2csv
</code></pre>
<h2 id="heading-step-2-get-access-to-the-content">Step 2: Get Access to the Content</h2>
<p>After installing all required dependencies, the next step is to access the HTML content on the page. To do this, you’ll use Puppeteer to send the request to the URL and then log the returned response to the console as shown:</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>

<span class="hljs-keyword">const</span> puppeteer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"puppeteer"</span>);

<span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://www.scrapingcourse.com/button-click"</span>;

<span class="hljs-keyword">const</span> scrapeFunction = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// Launch the browser and open a new blank page</span>
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> puppeteer.launch();
  <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> browser.newPage();

  <span class="hljs-comment">// Navigate to the target URL</span>
  <span class="hljs-keyword">await</span> page.goto(url);

  <span class="hljs-comment">// Get the URL’s HTML content</span>
  <span class="hljs-keyword">const</span> content = <span class="hljs-keyword">await</span> page.content();
  <span class="hljs-built_in">console</span>.log(content);

  <span class="hljs-comment">// Close the browser and all of its pages</span>
  <span class="hljs-keyword">await</span> browser.close();
};

scrapeFunction();
</code></pre>
<p>You can run the code in the <code>scrape.js</code> file using NodeJS by running the following command in the terminal:</p>
<pre><code class="lang-sh">node scrape.js
</code></pre>
<p>The output of the code returns the full HTML content of the website in the terminal window:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ie=edge"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Load More Button Challenge to Learn Web Scraping   - ScrapingCourse.com<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Bootstrap CSS --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">data-new-gr-c-s-check-loaded</span>=<span class="hljs-string">"14.1174.0"</span> <span class="hljs-attr">data-gr-ext-installed</span>=<span class="hljs-string">""</span> <span class="hljs-attr">itemscope</span> <span class="hljs-attr">itemtype</span>=<span class="hljs-string">"http://schema.org/WebPage"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Bootstrap and jQuery libraries --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.jquery.com/jquery-3.5.1.slim.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-step-3-load-more-products">Step 3: Load More Products</h2>
<p>Now that you have access to the URL content, you can interact with the page's content to load more products. For this tutorial, you’ll simulate an interaction with the "<strong>Load More</strong>" button at the bottom of the page.</p>
<p>To do this, you will use Puppeteer's <a target="_blank" href="http://page.click"><code>page.click</code></a><code>()</code> and <code>page.waitForSelector()</code> methods. The <a target="_blank" href="http://page.click"><code>page.click</code></a><code>()</code> method accepts the selector of a DOM element, scrolls it into view if required, and then simulates a mouse click at the center of the element. The <code>page.waitForSelector()</code> method, on the other hand, pauses the script until a node that matches a specific selector appears on the page. This method is helpful when waiting for a dynamic element on a page.</p>
<p>You must have retrieved the selector of the "<strong>Load More</strong>" button——<code>button#load-more-btn</code>—on the target page before using the <a target="_blank" href="http://page.click"><code>page.click</code></a><code>()</code> method. Also, for this tutorial, we'll be using a <code>for</code> loop to repeat the button click event till we have a minimum of 48 product cards on the target page.</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>

<span class="hljs-comment">//   Click the "Load More" button five times</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) {
  <span class="hljs-keyword">await</span> page.click(<span class="hljs-string">"button#load-more-btn"</span>);
}

<span class="hljs-comment">// wait for the 48th product card</span>
<span class="hljs-keyword">await</span> page.waitForSelector(<span class="hljs-string">".product-grid .product-item:nth-child(48)"</span>);
</code></pre>
<p>The <code>page.waitForSelector()</code> method accepts the selector of the 48th product item in the <code>product-grid</code> element class. This ensures that the rest of the program runs only after the 48th item is rendered.</p>
<p>At this point, your <code>scrape.js</code> file should look like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>

<span class="hljs-keyword">const</span> puppeteer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"puppeteer"</span>);

<span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://www.scrapingcourse.com/button-click"</span>;

<span class="hljs-keyword">const</span> scrapeFunction = <span class="hljs-keyword">async</span> () =&gt; {
 <span class="hljs-comment">// Launch the browser and open a new blank page</span>
 <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> puppeteer.launch();
 <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> browser.newPage();

 <span class="hljs-comment">// Navigate to the target URL</span>
 <span class="hljs-keyword">await</span> page.goto(url);

 <span class="hljs-comment">//   Click the "Load More" button a fixed number of times</span>
 <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) {
   <span class="hljs-keyword">await</span> page.click(<span class="hljs-string">"button#load-more-btn"</span>);
 }

 <span class="hljs-comment">// wait for the 48th product card</span>
 <span class="hljs-keyword">await</span> page.waitForSelector(<span class="hljs-string">".product-grid .product-item:nth-child(48)"</span>);

 <span class="hljs-comment">// Get the URL's HTML content</span>
 <span class="hljs-keyword">const</span> content = <span class="hljs-keyword">await</span> page.content();
 <span class="hljs-built_in">console</span>.log(content);

 <span class="hljs-comment">// Close the browser and all of its pages</span>
 <span class="hljs-keyword">await</span> browser.close();
};
scrapeFunction();
</code></pre>
<p>Again, you can run the NodeJS script in your terminal using the earlier command. The output of the code returns the full-page HTML content of the website in the terminal window:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ie=edge"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Load More Button Challenge to Learn Web Scraping   - ScrapingCourse.com<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Bootstrap CSS --&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- ... --&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Bootstrap and jQuery libraries --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.jquery.com/jquery-3.5.1.slim.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-step-4-parse-product-information">Step 4: Parse Product Information</h2>
<p>You can parse the product information since you now have the raw HTML data from the target page. Doing this converts the raw HTML data into a more readable format.</p>
<p>To begin, you’ll use Puppeteer's <code>page.evaluate()</code> method. This method allows you to run JavaScript functions within the context of the target page and returns a result. Then, create an empty array to collect the extracted data and retrieve the selector of the container element as shown:</p>
<pre><code class="lang-js"> <span class="hljs-comment">// extract product information and return an array of products</span>
 <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> page.evaluate(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">const</span> productList = [];
   productElements = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">".product-grid .product-item"</span>);

   <span class="hljs-comment">// parsing function goes here</span>

 <span class="hljs-keyword">return</span> productList;
 });
</code></pre>
<p>Next, loop through each product container returned to scrape through the data using each element’s unique selector. Then, push the data into the array created:</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>

<span class="hljs-comment">// extract product information and return an array of products</span>
 <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> page.evaluate(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">const</span> productList = [];
   productElements = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">".product-grid .product-item"</span>);

<span class="hljs-comment">// loop through each product to extract the data</span>
   productElements.forEach(<span class="hljs-function">(<span class="hljs-params">product</span>) =&gt;</span> {
     <span class="hljs-keyword">const</span> name = product.querySelector(<span class="hljs-string">"div span.product-name"</span>).textContent;
     <span class="hljs-keyword">const</span> imageLink = product
       .querySelector(<span class="hljs-string">"img.product-image"</span>)
       .getAttribute(<span class="hljs-string">"src"</span>);
     <span class="hljs-keyword">const</span> price = product.querySelector(<span class="hljs-string">"div span.product-price"</span>).textContent;
     <span class="hljs-keyword">const</span> url = product.querySelector(<span class="hljs-string">"a"</span>).getAttribute(<span class="hljs-string">"href"</span>);


    <span class="hljs-comment">// push the extracted data to the array created</span>
     productList.push({ name, imageLink, price, url });
   });
   <span class="hljs-keyword">return</span> productList;
 });
</code></pre>
<p>Finally, use NodeJS' file system module—<code>fs</code>—to create a new <code>products.json</code> file and write the array content to this file. You’ll also use JavaScript's <code>JSON.stringify()</code> method to convert the array data to JSON for readability.</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);

<span class="hljs-comment">// create a new JSON file and parse all the data to the file</span>
fs.writeFileSync(<span class="hljs-string">"products.json"</span>, <span class="hljs-built_in">JSON</span>.stringify(products, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data saved to products.json"</span>);
</code></pre>
<p>At this point, your <code>scrape.js</code> file should look like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>
<span class="hljs-keyword">const</span> puppeteer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"puppeteer"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);

<span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://www.scrapingcourse.com/button-click"</span>;

<span class="hljs-keyword">const</span> scrapeFunction = <span class="hljs-keyword">async</span> () =&gt; {
 <span class="hljs-comment">// Launch the browser and open a new blank page</span>
 <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> puppeteer.launch();
 <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> browser.newPage();

 <span class="hljs-comment">// Navigate to the target URL</span>
 <span class="hljs-keyword">await</span> page.goto(url);

 <span class="hljs-comment">//   Click the "Load More" button a fixed number of times</span>
 <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) {
   <span class="hljs-comment">// Click the "Load More" button</span>
   <span class="hljs-keyword">await</span> page.click(<span class="hljs-string">"button#load-more-btn"</span>);
 }

 <span class="hljs-comment">// wait for the 48th product card</span>
 <span class="hljs-keyword">await</span> page.waitForSelector(<span class="hljs-string">".product-grid .product-item:nth-child(48)"</span>);

 <span class="hljs-comment">// extract product information and return an array of products</span>
 <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> page.evaluate(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">const</span> productList = [];
   productElements = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">".product-grid .product-item"</span>);

   <span class="hljs-comment">// loop through each product to extract the data</span>
   productElements.forEach(<span class="hljs-function">(<span class="hljs-params">product</span>) =&gt;</span> {
     <span class="hljs-keyword">const</span> name = product.querySelector(<span class="hljs-string">"div span.product-name"</span>).textContent;
     <span class="hljs-keyword">const</span> imageLink = product
       .querySelector(<span class="hljs-string">"img.product-image"</span>)
       .getAttribute(<span class="hljs-string">"src"</span>);
     <span class="hljs-keyword">const</span> price = product.querySelector(<span class="hljs-string">"div span.product-price"</span>).textContent;
     <span class="hljs-keyword">const</span> url = product.querySelector(<span class="hljs-string">"a"</span>).getAttribute(<span class="hljs-string">"href"</span>);

     <span class="hljs-comment">// push the extracted data to the array created</span>
     productList.push({ name, imageLink, price, url });
   });
   <span class="hljs-keyword">return</span> productList;
 });

 <span class="hljs-comment">// create a new JSON file and parse all the data to the file</span>
 fs.writeFileSync(<span class="hljs-string">"products.json"</span>, <span class="hljs-built_in">JSON</span>.stringify(products, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));

 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data saved to products.json"</span>);

 <span class="hljs-comment">// Close the browser and all of its pages</span>
 <span class="hljs-keyword">await</span> browser.close();
};

scrapeFunction();
</code></pre>
<p>The output of the code returns a structured JSON in the <code>products.json</code> file with the product data:</p>
<pre><code class="lang-json">[
 {
   <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Chaz Kangeroo Hoodie"</span>,
   <span class="hljs-attr">"imageLink"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh01-gray_main.jpg"</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-string">"$52"</span>,
   <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/product/chaz-kangeroo-hoodie"</span>
 },
 {
   <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Teton Pullover Hoodie"</span>,
   <span class="hljs-attr">"imageLink"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh02-black_main.jpg"</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-string">"$70"</span>,
   <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/product/teton-pullover-hoodie"</span>
 },
 {
   <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Bruno Compete Hoodie"</span>,
   <span class="hljs-attr">"imageLink"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh03-black_main.jpg"</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-string">"$63"</span>,
   <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/product/bruno-compete-hoodie"</span>
 },
  {
   <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Ajax Full-Zip Sweatshirt"</span>,
   <span class="hljs-attr">"imageLink"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh12-green_main.jpg"</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-string">"$69"</span>,
   <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/product/ajax-full-zip-sweatshirt"</span>
 },
...
 {
   <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Mars HeatTech&amp;trade; Pullover"</span>,
   <span class="hljs-attr">"imageLink"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj10-red_main.jpg"</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-string">"$66"</span>,
   <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://scrapingcourse.com/ecommerce/product/mars-heattech&amp;trade;-pullover"</span>
 }
]
</code></pre>
<h2 id="heading-step-5-export-product-information-to-csv">Step 5: Export Product Information to CSV</h2>
<p>Alternatively, you can also export the product data into a CSV file. This will be an easy step since you already learned how to parse the information earlier.</p>
<p>To do this, you will use the <code>json2csv</code> package installed at the beginning of the tutorial.</p>
<pre><code class="lang-js"><span class="hljs-comment">// scrape.js</span>

<span class="hljs-keyword">const</span> json2csv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"json2csv"</span>).Parser;
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>).Parser;
 <span class="hljs-comment">// . . .   </span>

 <span class="hljs-comment">// initialize the package</span>
 <span class="hljs-keyword">const</span> parser = <span class="hljs-keyword">new</span> json2csv();

 <span class="hljs-comment">// create a new CSV file and parse the data to the file</span>
 <span class="hljs-keyword">const</span> productsCSV = parser.parse(products);
 fs.writeFileSync(<span class="hljs-string">"products.csv"</span>, productsCSV);

 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data saved to products.csv"</span>);
</code></pre>
<p>At this point, your <code>scrape.js</code> file should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> puppeteer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"puppeteer"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
<span class="hljs-keyword">const</span> json2csv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"json2csv"</span>).Parser;

<span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://www.scrapingcourse.com/button-click"</span>;

<span class="hljs-keyword">const</span> scrapeFunction = <span class="hljs-keyword">async</span> () =&gt; {
 <span class="hljs-comment">// Launch the browser and open a new blank page</span>
 <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> puppeteer.launch();
 <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> browser.newPage();

 <span class="hljs-comment">// Navigate to the target URL</span>
 <span class="hljs-keyword">await</span> page.goto(url);

 <span class="hljs-comment">//   Click the "Load More" button a fixed number of times</span>
 <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) {
   <span class="hljs-comment">// Click the "Load More" button</span>
   <span class="hljs-keyword">await</span> page.click(<span class="hljs-string">"button#load-more-btn"</span>);
 }

 <span class="hljs-comment">// wait for the 48th product card</span>
 <span class="hljs-keyword">await</span> page.waitForSelector(<span class="hljs-string">".product-grid .product-item:nth-child(48)"</span>);

 <span class="hljs-comment">// extract product information and return an array of products</span>
 <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> page.evaluate(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">const</span> productList = [];
   productElements = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">".product-grid .product-item"</span>);

   <span class="hljs-comment">// loop through each product to extract the data</span>
   productElements.forEach(<span class="hljs-function">(<span class="hljs-params">product</span>) =&gt;</span> {
     <span class="hljs-keyword">const</span> name = product.querySelector(<span class="hljs-string">"div span.product-name"</span>).textContent;
     <span class="hljs-keyword">const</span> imageLink = product
       .querySelector(<span class="hljs-string">"img.product-image"</span>)
       .getAttribute(<span class="hljs-string">"src"</span>);
     <span class="hljs-keyword">const</span> price = product.querySelector(<span class="hljs-string">"div span.product-price"</span>).textContent;
     <span class="hljs-keyword">const</span> url = product.querySelector(<span class="hljs-string">"a"</span>).getAttribute(<span class="hljs-string">"href"</span>);

     <span class="hljs-comment">// push the extracted data to the array created</span>
     productList.push({ name, imageLink, price, url });
   });
   <span class="hljs-keyword">return</span> productList;
 });


 <span class="hljs-comment">// initialize the package</span>
 <span class="hljs-keyword">const</span> parser = <span class="hljs-keyword">new</span> json2csv();

 <span class="hljs-comment">// create a new CSV file and parse the data to the file</span>
 <span class="hljs-keyword">const</span> productsCSV = parser.parse(products);
 fs.writeFileSync(<span class="hljs-string">"products.csv"</span>, productsCSV);

 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data saved to products.csv"</span>);

 <span class="hljs-comment">// Close the browser and all of its pages</span>
 <span class="hljs-keyword">await</span> browser.close();
};

scrapeFunction();
</code></pre>
<p>Again, you can run the NodeJS script in your terminal using the earlier command. The output of the code returns a structured CSV in the <code>products.csv</code> file with the product data:</p>
<pre><code class="lang-plaintext">"name","imageLink","price","url"
"Chaz Kangeroo Hoodie","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh01-gray_main.jpg","$52","https://scrapingcourse.com/ecommerce/product/chaz-kangeroo-hoodie"
"Teton Pullover Hoodie","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh02-black_main.jpg","$70","https://scrapingcourse.com/ecommerce/product/teton-pullover-hoodie"
"Bruno Compete Hoodie","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh03-black_main.jpg","$63","https://scrapingcourse.com/ecommerce/product/bruno-compete-hoodie"
"Frankie  Sweatshirt","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh04-green_main.jpg","$60","https://scrapingcourse.com/ecommerce/product/frankie--sweatshirt"
"Hollister Backyard Sweatshirt","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh05-white_main.jpg","$52","https://scrapingcourse.com/ecommerce/product/hollister-backyard-sweatshirt"
"Stark Fundamental Hoodie","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh06-blue_main.jpg","$42","https://scrapingcourse.com/ecommerce/product/stark-fundamental-hoodie"
"Hero Hoodie","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh07-gray_main.jpg","$54","https://scrapingcourse.com/ecommerce/product/hero-hoodie"
"Oslo Trek Hoodie","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh08-brown_main.jpg","$42","https://scrapingcourse.com/ecommerce/product/oslo-trek-hoodie"
"Kenobi Trail Jacket","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj04-black_main.jpg","$47","https://scrapingcourse.com/ecommerce/product/kenobi-trail-jacket"
"Jupiter All-Weather Trainer","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj06-blue_main.jpg","$56.99","https://scrapingcourse.com/ecommerce/product/jupiter-all-weather-trainer"
"Orion Two-Tone Fitted Jacket","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj07-red_main.jpg","$72","https://scrapingcourse.com/ecommerce/product/orion-two-tone-fitted-jacket"
"Lando Gym Jacket","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj08-gray_main.jpg","$99","https://scrapingcourse.com/ecommerce/product/lando-gym-jacket"
"Taurus Elements Shell","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj09-yellow_main.jpg","$65","https://scrapingcourse.com/ecommerce/product/taurus-elements-shell"
"Mars HeatTech&amp;trade; Pullover","https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mj10-red_main.jpg","$66","https://scrapingcourse.com/ecommerce/product/mars-heattech&amp;trade;-pullover"
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we’ve covered the basics of web scraping:</p>
<ul>
<li><p>Sending requests to access content on a webpage.</p>
</li>
<li><p>Loading additional content in a dynamic webpage.</p>
</li>
<li><p>Parsing information retrieved from the webpage.</p>
</li>
<li><p>Exporting information to JSON and CSV files.</p>
</li>
</ul>
<p>Whether you’re using open-source libraries or a web scraping service, these steps will help you get started with scraping dynamic content efficiently. Happy scraping!</p>
]]></content:encoded></item><item><title><![CDATA[How to Implement Seat-based Billing with Stripe Checkout]]></title><description><![CDATA[Modern SaaS businesses need a pricing model that aligns with customer usage and scales effectively. Seat-based billing is a common pricing model for SaaS businesses, allowing customers to pay a fee based on the number of seats or users they need each...]]></description><link>https://blog.dhera.dev/how-to-implement-seat-based-billing-with-checkout</link><guid isPermaLink="true">https://blog.dhera.dev/how-to-implement-seat-based-billing-with-checkout</guid><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Wed, 12 Mar 2025 17:20:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742329834917/7dbb1d0a-87c7-4319-95c7-beab67980071.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern SaaS businesses need a pricing model that aligns with customer usage and scales effectively. Seat-based billing is a common pricing model for SaaS businesses, allowing customers to pay a fee based on the number of seats or users they need each month.</p>
<p>In this article, we’ll explore how to implement a seat-based subscription model using Stripe Hosted Checkout for initial subscription creation and Stripe Customer Portal for post-purchase seat management.</p>
<p><a target="_blank" href="https://github.com/chideraao/seat-based-billing-tutorial">View implementation on Github</a>.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>In this section, we'll go through the requirements for the tutorial. To follow along with the rest of this guide, you will need the following:</p>
<ul>
<li><p>A <a target="_blank" href="https://dashboard.stripe.com/register">Stripe</a> account: You can use an account in test mode for now. However, ensure you enable live mode on your account when in production.</p>
</li>
<li><p>Node.js: You can download and install a NodeJS version with <em>Long-Term Support (LTS)</em> from the <a target="_blank" href="https://nodejs.org/en/download/package-manager">NodeJS download page</a>.</p>
</li>
<li><p>A code editor of choice. The tutorial assumes that you already have a product with a recurring billing setup. If you don't have one, see <a target="_blank" href="https://docs.stripe.com/products-prices/getting-started#create-products-prices">Create products and prices</a> on the Stripe documentation for more information.</p>
</li>
</ul>
<h2 id="heading-step-1-install-the-stripe-library">Step 1: Install the Stripe library</h2>
<p>To get started, we must first install the Stripe Node Library. This library is required to create a new checkout session on our application. To install the library, run the following command in the root of your project's directory:</p>
<pre><code class="lang-sh">npm install stripe express
</code></pre>
<p>The command also installs Express to the project. <em>Express</em> is a minimalistic back-end web application framework used for building applications with Node.js.</p>
<h2 id="heading-step-2-building-the-ui">Step 2: Building the UI</h2>
<p>Next, we need to build out the UI for the application. This step involves building a lightweight UI to handle the transition to Stripe's hosted checkout, as well as the <code>success.html</code> and <code>cancel.html</code> pages required by Stripe checkout.</p>
<h3 id="heading-create-the-checkouthtml">Create the <code>checkout.html</code></h3>
<p>For the <code>checkout.html</code>, we'll create a simple HTML page with a form and button that triggers Stripe's checkout session when clicked, as shown:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"/create-checkout-session"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"checkout-button"</span>&gt;</span>Checkout<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<h3 id="heading-create-the-success-and-cancel-pages">Create the success and cancel pages</h3>
<p>Next, let's create the success and cancel pages—<code>success.html</code> and <code>cancel.html</code>. Stripe requires these pages as they determine how our application handles redirects after a successful checkout or upon cancellation.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- success.html --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      We appreciate your business! If you have any questions, please email
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"mailto:orders@example.com"</span>&gt;</span>orders@example.com<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>.
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>For the cancel.html:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- cancel.html --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      Forgot to add something to your cart? Shop around then come back to pay!
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<h2 id="heading-step-3-setup-the-server">Step 3: Setup the server</h2>
<p>Now that we've built the UI, we can begin setting up the backend server that handles the checkout session.</p>
<p>First, we must import and initialize the Stripe and Express libraries that we installed earlier as shown:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server.js</span>
<span class="hljs-keyword">const</span> stripe = <span class="hljs-built_in">require</span>(<span class="hljs-string">"stripe"</span>)(<span class="hljs-string">"YOUR_STRIPE_SECRET_KEY"</span>);
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> app = express();
app.use(express.static(<span class="hljs-string">"public"</span>));

<span class="hljs-keyword">const</span> YOUR_DOMAIN = <span class="hljs-string">"http://localhost:4242"</span>;
</code></pre>
<h3 id="heading-create-the-checkout-session">Create the checkout session</h3>
<p>Now, we can begin setting up the checkout session. To begin, we must define an endpoint on the server that handles the form submission request from the front end. This endpoint must match the value of the <code>action</code> attribute on the <code>form</code> element in the <code>checkout.html</code> file. For this tutorial, we will be using <code>/create-checkout-session</code> as shown:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server.js</span>
app.post(<span class="hljs-string">"/create-checkout-session"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> stripe.checkout.sessions.create({
    <span class="hljs-attr">line_items</span>: [
      {
        <span class="hljs-comment">// Provide the Price ID of the recurring product</span>
        <span class="hljs-attr">price</span>: <span class="hljs-string">"PRICE_ID"</span>,
        <span class="hljs-attr">quantity</span>: <span class="hljs-number">4</span>,
      },
    ],
    <span class="hljs-attr">mode</span>: <span class="hljs-string">"subscription"</span>,
    <span class="hljs-attr">success_url</span>: <span class="hljs-string">`<span class="hljs-subst">${YOUR_DOMAIN}</span>/success.html`</span>,
    <span class="hljs-attr">cancel_url</span>: <span class="hljs-string">`<span class="hljs-subst">${YOUR_DOMAIN}</span>/cancel.html`</span>,
  });

  res.redirect(<span class="hljs-number">303</span>, session.url);
});
</code></pre>
<p>The <code>stripe.checkout.sessions.create</code> method creates a <a target="_blank" href="https://docs.stripe.com/api/checkout/sessions">Checkout Session</a>. A Checkout Session controls what your customer sees on the payment page, such as line items, the order amount, and acceptable payment methods.</p>
<p>Within the <code>line_items</code> array, provide the price ID of the product for which you want to implement seat-based billing within the <code>price</code> field and the number of initial seats within the <code>quantity</code> field. Each object in the array represents a product in your product catalog. To create seat-based billing, the price IDs of all products in the array must have a recurring price.</p>
<p>Next, include the values of the success and cancel URLs. These values must match the HTML pages created earlier. Since seat-based billing is a type of recurring payment, we must set the billing mode to <code>subscription</code> using the <code>mode</code> parameter.</p>
<p>At this point, our <code>server.js</code> should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> stripe = <span class="hljs-built_in">require</span>(<span class="hljs-string">"stripe"</span>)(<span class="hljs-string">"YOUR_STRIPE_SECRET_KEY"</span>);
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> app = express();
app.use(express.static(<span class="hljs-string">"public"</span>));

<span class="hljs-keyword">const</span> YOUR_DOMAIN = <span class="hljs-string">"http://localhost:4242"</span>;

app.post(<span class="hljs-string">"/create-checkout-session"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> stripe.checkout.sessions.create({
    <span class="hljs-attr">line_items</span>: [
      {
        <span class="hljs-comment">// Provide the exact Price ID (for example, pr_1234) of the product you want to sell</span>
        <span class="hljs-attr">price</span>: <span class="hljs-string">"PRICE_ID"</span>,
        <span class="hljs-attr">quantity</span>: <span class="hljs-number">4</span>,
      },
    ],
    <span class="hljs-attr">mode</span>: <span class="hljs-string">"subscription"</span>,
    <span class="hljs-attr">success_url</span>: <span class="hljs-string">`<span class="hljs-subst">${YOUR_DOMAIN}</span>/success.html`</span>,
    <span class="hljs-attr">cancel_url</span>: <span class="hljs-string">`<span class="hljs-subst">${YOUR_DOMAIN}</span>/cancel.html`</span>,
  });

  res.redirect(<span class="hljs-number">303</span>, session.url);
});

app.listen(<span class="hljs-number">4242</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Running on port 4242"</span>));
</code></pre>
<p>Now, we can test the application to ensure it works as desired. To begin:</p>
<ol>
<li><p>Start the application server and navigate to <a target="_blank" href="http://localhost:4242/checkout.html"><code>http://localhost:4242/checkout.html</code></a>.</p>
</li>
<li><p>Click the <strong>Checkout</strong> button to trigger the checkout session. This will redirect you to the Stripe Checkout page.</p>
</li>
<li><p>Fill in the required information and click the <strong>Subscribe</strong> button to create the subscription. This creates a new subscription for the product. You can find the new subscription on the <a target="_blank" href="https://dashboard.stripe.com/test/subscriptions?status=active">Subscriptions</a> page on your Stripe dashboard.</p>
</li>
</ol>
<h2 id="heading-upgrade-and-downgrade-seats">Upgrade and downgrade seats</h2>
<p>Stripe allows us to manage the subscription by making updates after creating a new seat-based billing subscription.</p>
<p>However, since Stripe Checkout only supports creating a new subscription, we must use an alternative method for updating our seat-based billing subscription. For this tutorial, we’ll use Stripe’s <a target="_blank" href="https://docs.stripe.com/customer-management">customer portal</a>. The customer portal allows customers to self-manage their payment details, invoices, and subscriptions.</p>
<p>To get started:</p>
<ol>
<li><p>Go to the <a target="_blank" href="https://dashboard.stripe.com/test/settings/billing/portal">customer portal configuration</a> page on the Stripe dashboard.</p>
</li>
<li><p>Click the <strong>Activate test link</strong> button.</p>
</li>
<li><p>Go to the <strong>Subscriptions</strong> menu on the portal configuration section.</p>
</li>
<li><p>Enable the <strong>Customers can switch plans</strong> and <strong>Customers can update the quantity of their plans</strong> options.</p>
</li>
<li><p>Next, select the product for which you created a subscription.</p>
</li>
<li><p>Finally, click the <strong>Save changes</strong> button to save the latest changes.</p>
</li>
</ol>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v866v0kkltzn1w6v1tt8.png" alt="Subscriptions configuration" /></p>
<p>After completing these steps, Stripe will generate a customer portal link that your customers can use to manage their subscriptions.</p>
<p>To update a subscription:</p>
<ol>
<li><p>Go to your customer portal link on a browser.</p>
</li>
<li><p>Enter your customer email to log in. The customer email you provide must be associated with an existing customer. Stripe automatically creates a new customer when a subscription is created, so you can use the customer email provided at checkout.</p>
</li>
<li><p>Click on the login link sent to the customer email provided.</p>
</li>
<li><p>Click the <strong>Update subscription</strong> button and then set the new number of seats to be purchased.</p>
</li>
<li><p>Click <strong>Continue &gt; Confirm</strong> to confirm the update.</p>
</li>
</ol>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/otebvgxmlgjcytw8on0i.png" alt="Update subscription" /></p>
<p>Note that in test mode, Stripe will only send authentication emails if you have a test mode customer associated with the email address provided.</p>
<h2 id="heading-next-steps">Next steps</h2>
<p>Using Stripe's Hosted Checkout and Customer Portal, you have learned how to create a new seat-based billing subscription and upgrade and downgrade seat count after purchase.</p>
<p>While this provides a solid foundation for per-seat billing, some notable limitations can create challenges for businesses managing seat-based subscriptions. For example, Stripe's proration invoices can be difficult to interpret, especially when customers upgrade or downgrade their seat count mid-cycle. The resulting invoice lacks clear line items that reflect standard industry practices.</p>
<p>Secondly, the simplicity of Stripe's approach forces businesses to build additional logic to enforce seat limits within their application. These gaps can lead to increased engineering effort and commitment for companies looking for a more robust and scalable solution.</p>
]]></content:encoded></item><item><title><![CDATA[How to Build an AI Agent for Voice-Enabled Payments with the Tether Wallet Development Kit]]></title><description><![CDATA[Voice-enabled payments are revolutionizing how users interact with financial applications, offering a seamless and intuitive way to manage transactions. Using Wallet Development Kit (WDK) by Tether, developers can create powerful, AI-driven solutions...]]></description><link>https://blog.dhera.dev/building-an-ai-agent-for-voice-enabled-payments-with-the-tether-wdk</link><guid isPermaLink="true">https://blog.dhera.dev/building-an-ai-agent-for-voice-enabled-payments-with-the-tether-wdk</guid><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Tue, 04 Mar 2025 11:13:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742329424650/5d1253f2-a553-48be-97e4-9dd7fca7c9bb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Voice-enabled payments are revolutionizing how users interact with financial applications, offering a seamless and intuitive way to manage transactions. Using <strong>Wallet Development Kit</strong> (<strong>WDK</strong>) by Tether, developers can create powerful, AI-driven solutions for modern blockchain payment.</p>
<p>In this guide, we'll walk through building an AI Agent for voice-enabled payments using the Tether WDK. You'll learn how to set up the development environment, understand the core components of the WDK, and implement a fully functional voice-enabled payment agent. By the end of this tutorial, you'll have a working example and the knowledge to expand its capabilities further.</p>
<p>Let's get right to it!</p>
<h2 id="heading-what-is-the-tether-wdk">What is the Tether WDK?</h2>
<p>The <a target="_blank" href="https://docs.wallet.tether.io/"><em>Tether WDK</em></a> is a multi-asset cryptocurrency wallet library developed by Tether. It enables businesses and developers to seamlessly integrate advanced wallet-related functionalities and user experiences for Bitcoin and USD₮ into any website, app, or device.</p>
<p>The library is prebuilt with all the necessary components to create a wallet. These include:</p>
<ul>
<li><p><a target="_blank" href="https://docs.wallet.tether.io/components/wallet-seed">Wallet seed</a>: This component is a sublibrary within the Tether WDK library that handles BIP39 seed generation and management. The component is used to generate BIP39 seed phrases for all assets.</p>
</li>
<li><p><a target="_blank" href="https://docs.wallet.tether.io/components/wallet-store">Wallet store</a>: This component is a sublibrary within the Tether WDK library wallet that stores data. It supports multiple storage engine implementations, allowing developers to implement the storage engine that best suits their projects' needs.</p>
</li>
<li><p><a target="_blank" href="https://docs.wallet.tether.io/components/wallet-indexer">Wallet indexer</a>: This component is a remote blockchain data provider. It seamlessly integrates JSON-RPC and WebSocket APIs in the background to fetch and deliver real-time blockchain data efficiently.</p>
</li>
<li><p><a target="_blank" href="https://docs.wallet.tether.io/components/wallet-test-tools">Wallet test-tools</a>: This component contains tools used for developing and testing the Tether WDK. It supports setting up test environments for both Bitcoin and Ethereum local networks.</p>
</li>
</ul>
<p>In addition to the Tether WDK's prebuilt components, the library also supports the creation of custom components, allowing you to build components to address your use case.</p>
<h2 id="heading-building-the-ai-agent">Building the AI Agent</h2>
<p>Now that we've learned about the Tether WDK and its components, let's use the library to build an AI agent for voice-enabled payments.</p>
<h3 id="heading-step-1-prerequisites">Step 1: Prerequisites</h3>
<p>In this section, we'll go through all the steps, dependencies, and installations required for this tutorial. To follow along with the rest of this guide, you will need the following:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/cculianu/Fulcrum">Fulcrum Electrum</a>: <em>Fulcrum</em> is a fast and light <strong>Simplified Payment Verification</strong> (<strong>SPV</strong>) server for Bitcoin Cash, Bitcoin BTC, and Litecoin.</p>
</li>
<li><p><a target="_blank" href="https://github.com/fedirz/faster-whisper-server">speaches</a>: <em>speaches</em> is an OpenAI API-compatible server supporting streaming transcription, translation, and speech generation. An active speaches instance is required to follow along with this tutorial.</p>
</li>
<li><p><a target="_blank" href="https://ollama.com/library/llama3.1:8b">Ollama</a>: <em>Ollama</em> is a popular <strong>Large Language Model</strong> (<strong>LLM</strong>) backend used to run and serve LLMs offline. An active Ollama instance is required for this tutorial.</p>
</li>
<li><p>NodeJS: You can download and install a NodeJS version with <em>Long-Term Support</em> (<em>LTS</em>) from the <a target="_blank" href="https://nodejs.org/en/download/package-manager">NodeJS download page</a>. This installation adds NodeJS to your machine's directory and allows you to install dependencies with the <em>Node Package Manager</em> (<em>NPM</em>).</p>
</li>
<li><p>A code editor of choice.</p>
</li>
</ul>
<p>With the environment setup completed, you can start setting up NodeJS for your project. To begin, create a new folder in a directory of choice and create an <code>index.js</code> file.</p>
<p>Next, you must initialize NodeJS in the newly created folder. To initialize NodeJS, run the following command at the root of the folder's directory:</p>
<pre><code class="lang-sh">npm init -y
</code></pre>
<p>This command creates a project scaffold with the <code>node_modules</code> folder and <code>package.json</code> file that contains our project's dependencies. Next, we need to download the Tether WDK. To do this, run the following command in the terminal at the root of your project's directory</p>
<pre><code class="lang-sh">npm install github:tetherto/lib-wallet<span class="hljs-comment">#v0.0.1</span>
</code></pre>
<p>This command pulls the Tether WDK library—<code>lib-wallet</code>—directly from the Github repository and updates both the <code>node_modules</code> folder and <code>package_json</code> file with the necessary dependencies.</p>
<h3 id="heading-step-2-render-the-ui">Step 2: Render the UI</h3>
<p>After installing all the dependencies and libraries, we can begin working on the UI. In this step, we'll create an example address book to enable us interact with our final application. We'll also define two JavaScript functions—<code>renderAddressBook</code> and <code>renderAddresses</code>— that enable us interact with the addresses on the screen. These functions set up the basic UI for recording audio, transcribing it, and performing wallet actions based on the transcription.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Example address book</span>
<span class="hljs-keyword">const</span> book = {
  <span class="hljs-attr">bob</span>: {
    <span class="hljs-attr">btc</span>: <span class="hljs-string">"bcrt1qrfd2ujntu7la5vjqpjr69u8tc8rl6fxvx6hrzm"</span>,
  },
  <span class="hljs-attr">alice</span>: {
    <span class="hljs-attr">btc</span>: <span class="hljs-string">"bcrt1q7mm7seyccvf4dyc2je97zumh4aes7xhgetwc6m"</span>,
    <span class="hljs-attr">eth</span>: {
      <span class="hljs-attr">usdt</span>: <span class="hljs-string">"0x9ede22b627388b5db43c3488f27480b45d22d238"</span>,
    },
  },
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderAddressBook</span>(<span class="hljs-params">book</span>) </span>{
  <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"div"</span>);
  container.className = <span class="hljs-string">"address-book"</span>;

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [name, addresses] <span class="hljs-keyword">of</span> <span class="hljs-built_in">Object</span>.entries(book)) {
    <span class="hljs-keyword">const</span> personElement = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"div"</span>);
    personElement.className = <span class="hljs-string">"person"</span>;

    <span class="hljs-keyword">const</span> nameElement = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"h3"</span>);
    nameElement.textContent = name;
    personElement.appendChild(nameElement);

    <span class="hljs-keyword">const</span> addressList = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"ul"</span>);
    renderAddresses(addresses, addressList);
    personElement.appendChild(addressList);

    container.appendChild(personElement);
  }
  <span class="hljs-keyword">const</span> node = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"addr"</span>);
  node.appendChild(container);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderAddresses</span>(<span class="hljs-params">addresses, parentElement, prefix = <span class="hljs-string">""</span></span>) </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [key, value] <span class="hljs-keyword">of</span> <span class="hljs-built_in">Object</span>.entries(addresses)) {
    <span class="hljs-keyword">const</span> listItem = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"li"</span>);

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value === <span class="hljs-string">"string"</span>) {
      listItem.textContent = <span class="hljs-string">`<span class="hljs-subst">${prefix}</span><span class="hljs-subst">${key}</span>: <span class="hljs-subst">${value}</span>`</span>;
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value === <span class="hljs-string">"object"</span>) {
      listItem.textContent = <span class="hljs-string">`<span class="hljs-subst">${prefix}</span><span class="hljs-subst">${key}</span>:`</span>;
      <span class="hljs-keyword">const</span> nestedList = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"ul"</span>);
      renderAddresses(value, nestedList, <span class="hljs-string">"  "</span>);
      listItem.appendChild(nestedList);
    }

    parentElement.appendChild(listItem);
  }
}
</code></pre>
<p>You can add appropriate styling to these elements as required.</p>
<h3 id="heading-step-3-setup-the-wallet">Step 3: Setup the wallet</h3>
<p>Now, we can begin setting up the wallet to be used for the project. To do this, we will use Tether's WDK library. Before using the library, we must first import the required libraries into the <code>index.js</code> file. For this tutorial, we will import both the Tether WDK library and the Wallet seed sublibrary. To do this, copy the following snippet and paste at the top of your <code>index.js</code> file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Wallet = <span class="hljs-built_in">require</span>(<span class="hljs-string">"lib-wallet/src/wallet-lib"</span>);
<span class="hljs-keyword">const</span> Bip39Seed = <span class="hljs-built_in">require</span>(<span class="hljs-string">"lib-wallet-seed-bip39"</span>);
</code></pre>
<p>Next, let's generate a seed phrase and define the wallet configuration. To generate a new seed phrase, we used the <code>generate</code> method within the Wallet seed library's <code>Bip39Seed</code> class:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Generate a new phrase on page refresh</span>
<span class="hljs-keyword">const</span> PHRASE = <span class="hljs-keyword">await</span> Bip39Seed.generate();

<span class="hljs-comment">// Wallet config</span>
<span class="hljs-keyword">const</span> wconfig = {
  <span class="hljs-attr">network</span>: <span class="hljs-string">"regtest"</span>,
  <span class="hljs-attr">electrum_host</span>: <span class="hljs-string">"ws://localhost"</span>,
  <span class="hljs-attr">electrum_port</span>: <span class="hljs-string">"8001"</span>,
  <span class="hljs-attr">token_contract</span>: <span class="hljs-string">"0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1"</span>,
  <span class="hljs-attr">web3_indexer_ws</span>: <span class="hljs-string">"ws://localhost/eth/hardhat/indexer/ws"</span>,
  <span class="hljs-attr">web3_indexer</span>: <span class="hljs-string">"http://localhost/eth/hardhat/indexer/rpc"</span>,
  <span class="hljs-attr">web3</span>: <span class="hljs-string">"ws://localhost/eth/hardhat/indexer/web3"</span>,
  <span class="hljs-attr">seed</span>: {
    <span class="hljs-attr">mnemonic</span>: PHRASE,
  },
};
</code></pre>
<p>Finally, let's define a function to initialize the wallet as shown:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initWallet</span>(<span class="hljs-params"></span>) </span>{
  renderAddressBook(book);
  <span class="hljs-keyword">const</span> w = <span class="hljs-keyword">await</span> Wallet.wallet(wconfig);

  <span class="hljs-keyword">await</span> w.syncHistory({ <span class="hljs-attr">all</span>: <span class="hljs-literal">true</span> });
  <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"seed"</span>).textContent = w.seed.mnemonic;
  Wallet.demoWallet = w;

  <span class="hljs-keyword">return</span> w;
}
</code></pre>
<p>When run, the <code>initWallet</code> function will initialize the wallet using the configuration defined in <code>wconfig</code>, sync the wallet's transaction history, and then display the generated seed phrase on the page.</p>
<h3 id="heading-step-4-wallet-interactions">Step 4: Wallet interactions</h3>
<p>Now that we have successfully set up the wallet, we can define interactions that a user can perform on the wallet. To do this, we need to create a function that performs actions on the wallet based on the parsed transcription. The function also includes conditionals that determine the error messages if a user attempts to perform an unsupported action or use an unsupported asset.</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">walletAction</span>(<span class="hljs-params">msg</span>) </span>{
  <span class="hljs-keyword">const</span> wallet = Wallet.demoWallet;

  <span class="hljs-keyword">const</span> asset = wallet.pay[msg.asset.toLowerCase()];
  <span class="hljs-keyword">if</span> (!asset) <span class="hljs-keyword">return</span> setStatus(<span class="hljs-string">`asset: <span class="hljs-subst">${msg.asset}</span> is not supported`</span>);
  <span class="hljs-keyword">if</span> (!asset[msg.action])
    <span class="hljs-keyword">return</span> setStatus(<span class="hljs-string">`action <span class="hljs-subst">${msg.action}</span> not supported by wallet`</span>);

  <span class="hljs-keyword">if</span> (msg.args) {
    msg.args.fee = <span class="hljs-number">10</span>;
  }

  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> wallet.pay[msg.asset.toLowerCase()][msg.action](
    { <span class="hljs-attr">token</span>: msg.token?.toLowerCase() },
    msg.args
  );

  <span class="hljs-keyword">try</span> {
    setStatus(<span class="hljs-built_in">JSON</span>.stringify(res, <span class="hljs-literal">null</span>, <span class="hljs-number">1</span>));
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.log(err);
    setStatus(<span class="hljs-string">"command failed"</span>);
  }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setStatus</span>(<span class="hljs-params">txt</span>) </span>{
  <span class="hljs-keyword">const</span> statusDiv = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"status"</span>);
  statusDiv.textContent = txt;
}
</code></pre>
<h3 id="heading-step-5-set-up-voice-recording">Step 5: Set up voice recording</h3>
<p>The first phase involved in this step is to define constants for the active Ollama and speaches instances. To do this, copy the following lines of code and paste them into your <code>index.js</code> file. The link to each instance must be the same as the link on your local device.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Edit these to your local instances</span>
<span class="hljs-keyword">const</span> SPEACHES = <span class="hljs-string">"http://localhost/whispr/audio/transcriptions"</span>;
<span class="hljs-keyword">const</span> OLLAMA = <span class="hljs-string">"http://localhost:11434/api/chat"</span>;
</code></pre>
<p>Next, let's define a function that handles audio interaction on the app. The <code>initMic</code> function initializes the microphone, starts/stops recording, and then uploads and sends the audio to the <code>SPEACHES</code> service for transcription.</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initMic</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> mediaRecorder;
  <span class="hljs-keyword">let</span> audioChunks = [];
  <span class="hljs-keyword">let</span> isRecording = <span class="hljs-literal">false</span>;

  <span class="hljs-keyword">const</span> recordButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"record"</span>);
  <span class="hljs-keyword">const</span> audioPlayback = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"audio"</span>);

  recordButton.onclick = toggleRecording;

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toggleRecording</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (!isRecording) {
      <span class="hljs-keyword">await</span> startRecording();
    } <span class="hljs-keyword">else</span> {
      stopRecording();
    }
  }

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startRecording</span>(<span class="hljs-params"></span>) </span>{
    audioChunks = [];
    <span class="hljs-keyword">const</span> stream = <span class="hljs-keyword">await</span> navigator.mediaDevices.getUserMedia({ <span class="hljs-attr">audio</span>: <span class="hljs-literal">true</span> });
    <span class="hljs-keyword">const</span> mediaRecorder = <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.MediaRecorder(stream, {
      <span class="hljs-attr">mimeType</span>: <span class="hljs-string">"audio/ogg; codecs=opus"</span>,
    });

    mediaRecorder.ondataavailable = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      audioChunks.push(event.data);
    };

    mediaRecorder.onstop = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">const</span> audioBlob = <span class="hljs-keyword">new</span> Blob(audioChunks, {
        <span class="hljs-attr">type</span>: <span class="hljs-string">"audio/ogg; codecs=opus"</span>,
      });
      <span class="hljs-keyword">const</span> audioUrl = URL.createObjectURL(audioBlob);
      audioPlayback.src = audioUrl;

      <span class="hljs-keyword">await</span> uploadAudio(audioBlob);
    };

    mediaRecorder.start();
    isRecording = <span class="hljs-literal">true</span>;
    recordButton.classList.add(<span class="hljs-string">"recording"</span>);
    setStatus(<span class="hljs-string">"Recording.... (Press again to stop recording)"</span>);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stopRecording</span>(<span class="hljs-params"></span>) </span>{
    mediaRecorder.stop();
    isRecording = <span class="hljs-literal">false</span>;
    recordButton.classList.remove(<span class="hljs-string">"recording"</span>);
  }

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadAudio</span>(<span class="hljs-params">audioBlob</span>) </span>{
    <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData();
    formData.append(<span class="hljs-string">"file"</span>, audioBlob, <span class="hljs-string">"recording.ogg"</span>);
    formData.append(<span class="hljs-string">"language"</span>, <span class="hljs-string">"en"</span>);

    setStatus(<span class="hljs-string">"Uploading..."</span>);
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(SPEACHES, {
      <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
      <span class="hljs-attr">body</span>: formData,
    });

    <span class="hljs-keyword">if</span> (response.ok) {
      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.json();
      setStatus(<span class="hljs-string">`transcribed: <span class="hljs-subst">${result.text}</span>. processing ....`</span>);
      parseTranscribe(result.text);
    } <span class="hljs-keyword">else</span> {
      setStatus(<span class="hljs-string">"Upload failed"</span>);
    }
  }
}
</code></pre>
<h3 id="heading-step-6-parse-and-process-audio-transcriptions">Step 6: Parse and process audio transcriptions</h3>
<p>Upon retrieving the transcribed text, we can send it to the Ollama LLM instance service for further processing. It expects a structured JSON response. Therefore, we must parse the returned data before sending it for processing, as shown:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseTranscribe</span>(<span class="hljs-params">txt</span>) </span>{
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">model</span>: <span class="hljs-string">"llama3.1"</span>,
    <span class="hljs-attr">stream</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">messages</span>: [
      {
        <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>,
        <span class="hljs-attr">content</span>: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify({
          asset: <span class="hljs-string">""</span>,
          token: <span class="hljs-string">""</span>,
          action: <span class="hljs-string">""</span>,
          args: {
            amount: <span class="hljs-string">""</span>,
            unit: <span class="hljs-string">""</span>,
            address: <span class="hljs-string">""</span>,
          }</span>,
          addressBook: book,
          text: txt,
        })}`</span>,
      },
    ],
  };

  <span class="hljs-keyword">let</span> msg;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(OLLAMA, {
      <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
      <span class="hljs-attr">headers</span>: {
        <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
      },
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(data),
    });

    <span class="hljs-keyword">if</span> (!response.ok) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP error! status: <span class="hljs-subst">${response.status}</span>`</span>);
    }

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.json();
    msg = <span class="hljs-built_in">JSON</span>.parse(result.message.content);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error:"</span>, error);
    <span class="hljs-keyword">throw</span> error;
  }
  walletAction(msg);
}
</code></pre>
<p>Now, we can bring all the above steps together. But first, we need to define an asynchronous function—<code>main</code>—that calls the <code>initWallet</code> and <code>initMic</code> functions. Then, we call the <code>main</code> function when the DOM is fully loaded.</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">await</span> initWallet();
  <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"record"</span>).textContent = <span class="hljs-string">"Record"</span>;
  initMic();
}

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"DOMContentLoaded"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  main();
});
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By following the steps outlined in this tutorial, you've covered the following:</p>
<ul>
<li><p>Building an AI Agent for voice-enabled payments using the Tether WDK.</p>
</li>
<li><p>Setting up the development environment, configuring essential components, and integrating the wallet library to handle blockchain transactions.</p>
</li>
<li><p>Implemented voice commands to streamline user interactions using an AI agent.</p>
</li>
</ul>
<p>With this foundation, you can extend the AI Agent to support more advanced use cases and customize features to meet specific requirements.</p>
<p>The possibilities are endless—happy building!</p>
]]></content:encoded></item><item><title><![CDATA[Understanding JavaScript Deobfuscation]]></title><description><![CDATA[One of the most challenging obstacles developers face when web scraping is JavaScript obfuscation. Websites often employ obfuscation tools to prevent scraping or hide sensitive operations within them. 
Understanding how to deobfuscate with Javascript...]]></description><link>https://blog.dhera.dev/understanding-javascript-deobfuscation</link><guid isPermaLink="true">https://blog.dhera.dev/understanding-javascript-deobfuscation</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Wed, 26 Feb 2025 23:00:00 GMT</pubDate><content:encoded><![CDATA[<p>One of the most challenging obstacles developers face when web scraping is <em>JavaScript obfuscation</em>. Websites often employ obfuscation tools to prevent scraping or hide sensitive operations within them. </p>
<p>Understanding how to deobfuscate with Javascript is crucial when scraping complex websites. This article will help you understand what JavaScript obfuscation means, why it's used, and how to approach deobfuscating JavaScript code during web scraping.</p>
<h2 id="heading-what-is-obfuscation"><strong>What is obfuscation?</strong></h2>
<p>Obfuscation is the process of transforming your code to make it hard to steal or copy. <em>JavaScript obfuscation</em>, therefore, is the process of making JavaScript code difficult to copy or reverse engineer. It involves transforming human-readable code into a more cryptic form without affecting its functionality. This technique is a helpful security measure that helps websites prevent malicious activities, protect sensitive operations, and deter automated scraping tools.</p>
<h2 id="heading-what-is-javascript-deobfuscation"><strong>What is JavaScript deobfuscation?</strong></h2>
<p>JavaScript deobfuscation is the process of reversing or decoding obfuscated JavaScript code. This process is valuable for several reasons, including analyzing malicious scripts, finding and fixing bugs, and allowing efficient website scraping.</p>
<p>JavaScript deobfuscation is an essential skill for web scraping developers when dealing with websites that obscure their JavaScript code to protect content or hide sensitive operations. As modern web scraping techniques increasingly rely on interacting with JavaScript for rendering or dynamically loading content, understanding how to deobfuscate code allows developers to extract data more effectively and bypass barriers placed by websites.</p>
<h2 id="heading-importance-of-javascript-deobfuscation"><strong>Importance of JavaScript deobfuscation</strong></h2>
<p>Obfuscated code can present a significant barrier for web scraping developers. Without proper deobfuscation techniques, you may miss critical data, like hidden API endpoints or vital product information.</p>
<p>Understanding deobfuscation is a relevant skill for web scraping developers today for the following reasons: </p>
<ul>
<li><p><strong>Accessing Hidden Data:</strong> Some websites load content via obfuscated JavaScript functions, which prevents web scrapers from extracting information efficiently.</p>
</li>
<li><p><strong>Bypassing Anti-Scraping Measures:</strong> Deobfuscating JavaScript can reveal clues about how a site detects and prevents scraping, allowing developers to adjust their techniques accordingly.</p>
</li>
<li><p><strong>Understanding Dynamic Content:</strong> Many modern websites use JavaScript to load data dynamically. Obfuscated code makes extracting data from these websites difficult for web scrapers.</p>
</li>
</ul>
<h2 id="heading-techniques-for-javascript-deobfuscation"><strong>Techniques for JavaScript deobfuscation</strong></h2>
<p>Deobfuscation involves a combination of techniques and tools to reverse-engineer the altered JavaScript code. The general process involves:</p>
<ul>
<li><p><strong>Analyzing the structure:</strong> This involves examining the structure of the obfuscated code to get an idea of how it works. When analyzing the structure, it is helpful to look for common patterns within the code. These include altered variable names, code splitting, encoded strings or functions, etc.</p>
</li>
<li><p><strong>Reverse engineering:</strong> Reverse engineering helps developers dissect and understand the underlying operations. For example, check out <a target="_blank" href="https://www.zenrows.com/blog/bypass-cloudflare#bypass-cloudflare-waiting-room">this article</a> on reverse engineering Cloudflare's anti-bot challenge.</p>
</li>
<li><p><strong>Using deobfuscation Tools:</strong> There are several tools available to help simplify obfuscated code. They help you find obfuscation patterns and decipher complex code structures. Tools like <em>JSBeautifier</em> reformat obfuscated JavaScript into a more readable structure by adding indentation and restoring standard syntax. This can be a good first step to making obfuscated code easier to analyze. Examples of deobfuscation tools include JS Beautifier and JavaScript Deobfuscator. For example, <a target="_blank" href="https://www.zenrows.com/blog/bypass-akamai#deobfuscate-the-challenge">this article</a> demonstrates how to bypass Akamai’s JavaScript challenge using the JavaScript Deobfuscator tool.</p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>JavaScript deobfuscation is a crucial skill for web scraping developers as websites increasingly employ obfuscation techniques to protect their data and logic. Learning to reverse-engineer obfuscated code allows you to access hidden APIs, parse complex data, and continue scraping efficiently. The right tools and techniques can simplify this challenge and help you overcome complex obstacles in your web scraping projects.</p>
]]></content:encoded></item><item><title><![CDATA[Data Visualization: How to Create Styled Cryptocurrency Candlesticks with Highcharts]]></title><description><![CDATA[https://codepen.io/chideraao/pen/VwzgBbj
 
What is Data Visualization?
Data visualization is the practice of representing data/information in pictorial or graphical formats. It is a means by which large data sets or metrics are converted into visual ...]]></description><link>https://blog.dhera.dev/data-visualization-how-to-create-styled-cryptocurrency-candlesticks-with-highcharts</link><guid isPermaLink="true">https://blog.dhera.dev/data-visualization-how-to-create-styled-cryptocurrency-candlesticks-with-highcharts</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[DataVisualization]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Thu, 26 Dec 2024 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741907607973/a089bdc7-eb0d-4503-bcef-f8ef75eebcc9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/chideraao/pen/VwzgBbj">https://codepen.io/chideraao/pen/VwzgBbj</a></div>
<p> </p>
<h3 id="heading-what-is-data-visualization">What is Data Visualization?</h3>
<p>Data visualization is the practice of representing data/information in pictorial or graphical formats. It is a means by which large data sets or metrics are converted into visual elements like maps, graphs, and charts, which are much more appealing to an end-user.</p>
<p>The JavaScript ecosystem currently has several reliable, first-rate libraries for data visualization. Some of which include D3.js, Highcharts, Charts.js, Rechart, etc. However, in this article, we will be using Highcharts to create our charts.</p>
<hr />
<p>Highcharts is a JavaScript library for creating SVG-based, responsive, and interactive charts for web and mobile. It provides deep customization of charts via JavaScript or CSS. Highcharts offers four product categories for creating charts. These include:</p>
<ul>
<li><p><strong>Highcharts:</strong> This is the basic Highcharts module that is required in all charts. It can be used to create simple line, bar and pie charts.</p>
</li>
<li><p><strong>Highcharts Stock:</strong> This is used for creating general stock and timeline charts for your applications. Some examples include simple <a target="_blank" href="https://codepen.io/chideraao/pen/dyzKyJx">Stock charts</a>, <a target="_blank" href="https://codepen.io/chideraao/pen/mdMKdQP">Candlesticks &amp; Heikin-Ashi</a>, <a target="_blank" href="https://codepen.io/chideraao/pen/XWaYJjL">OHLC</a>. You can also make use of the Stock Tools module which provides a GUI that permits interaction with charts.</p>
</li>
<li><p><strong>Highcharts Maps:</strong> Highcharts also provides an option to generate schematic maps which allow developers add interactive, customizable maps to their web application. It offers options to either use <a target="_blank" href="https://code.highcharts.com/mapdata/">map collection</a> provided by Highcharts or create custom SVG maps to suit your purpose.</p>
</li>
<li><p><strong>Highcharts Gantt:</strong> This is a special type of bar chart used for illustrating project schedules.</p>
</li>
</ul>
<p>We will use the Highcharts Stock to create styled candlesticks with oscillators and technical indicators provided by the Stock Tools module.</p>
<h2 id="heading-creating-the-candlestick">Creating the Candlestick</h2>
<p>A candlestick chart( or Japanese candlestick chart) is a style of financial chart used by traders to determine possible price movements of a stock, security, or currency based on previous patterns. It makes use of key price points or OHLC(open, high, low, close) values taken at regular intervals for a specified period of time.</p>
<p>Not to be confused with the typical candlestick chart is the Heikin-Ashi('average bar') chart. Although identical to the candlestick chart, it is mostly used in conjunction with the candlestick as it helps make candlestick chart trends easier to analyze. Hence, making it more readable.</p>
<p>The Highcharts API provides options for creating both candlestick charts and Heikin-Ashi charts. This article focuses on candlestick charts; however, I will point out the tradeoffs required for creating an Heikin-Ashi chart along the way. Let's get our hands dirty, shall we?!</p>
<h3 id="heading-getting-started">Getting Started</h3>
<p>To begin using Highcharts, we must first download Highcharts. Highcharts provides several options to introduce Highcharts into your project. You can choose to either:</p>
<ul>
<li><p><strong>Download</strong> the entire <a target="_blank" href="https://code.highcharts.com/zips/Highcharts-9.3.1.zip">Highcharts library</a>. Depending on your use case, you can also download the Highcharts <a target="_blank" href="https://code.highcharts.com/zips/Highcharts-Stock-9.3.1.zip">Stock</a>, <a target="_blank" href="https://code.highcharts.com/zips/Highcharts-Maps-9.3.1.zip">Maps</a>, or <a target="_blank" href="https://code.highcharts.com/zips/Highcharts-Gantt-9.3.1.zip">Gantt</a> libraries.</p>
</li>
<li><p><strong>Install Highcharts</strong> via <a target="_blank" href="https://www.npmjs.com/package/highcharts">NPM</a> and import as modules. These are best for Single Page Applications like React and Vue.</p>
</li>
<li><p><strong>Use</strong> the <a target="_blank" href="https://code.highcharts.com/">Highcharts CDN</a> to access files directly.</p>
</li>
</ul>
<p>We will be using the Highcharts CDN for this article.</p>
<h3 id="heading-the-html">The HTML</h3>
<p>The bulk of the HTML contains script tags used to load the Highcharts CDN. The first three are required modules for all stock charts created with Highcharts.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/stock/highstock.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/stock/modules/data.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/stock/modules/exporting.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Unlike candlestick charts, if you need to create a Heikin-Ashi chart, you will need to bring in the module separately as below:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/stock/modules/heikinashi.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>The final CDN we need to bring into our application is the Stock Tools module. This enables us to make use of technical indicators. The Stock Tools module must be loaded last so it can pick up all available modules from above.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/stock/indicators/indicators-all.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Rather than loading all technical indicators from the Stock Tools module, you can also load specific indicators depending on your needs:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/indicators/indicators.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/indicators/rsi.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/indicators/ema.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.highcharts.com/indicators/macd.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Lastly, we need to create an HTML element to hold our chart that we can reference from the JavaScript:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"container"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"height: 500px; min-width: 310px"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h3 id="heading-the-javascript">The JavaScript</h3>
<p><strong>Bringing in our Data</strong> The first item on our itinerary is to bring in the data we will be plotting. Highcharts provides a <code>.getJSON</code> method similar to that of jQuery, which is used for making HTTP requests. It also provides a <code>stockChart</code> class for creating the chart. The <code>stockChart</code> class takes in two parameters:</p>
<ul>
<li><p>The first parameter, <code>renderTo</code>, is the DOM element or the id of the DOM element to which the chart should render.</p>
</li>
<li><p>The second parameter, <code>options</code>, are the options that structure the chart.</p>
</li>
</ul>
<pre><code class="lang-javascript">Highcharts.getJSON(<span class="hljs-string">'https://api.coingecko.com/api/v3/coins/ethereum/ohlc?vs_currency=usd&amp;days=365'</span>, 
<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">candlestick</span>) </span>{
  <span class="hljs-comment">// create the chart</span>
  Highcharts.stockChart(<span class="hljs-string">'container'</span>, {
    <span class="hljs-attr">title</span>: {
      <span class="hljs-attr">text</span>: <span class="hljs-string">'Untitled Masterpiece'</span>
    },

    <span class="hljs-attr">series</span>: [
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">"candlestick"</span>,    <span class="hljs-comment">//heikinashi for Heikin-Ashi chart</span>
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Ethereum"</span>,      <span class="hljs-comment">//chart name</span>
        <span class="hljs-attr">id</span>: <span class="hljs-string">"eth"</span>,             <span class="hljs-comment">// chart id, useful when adding indicators and oscillators</span>
        <span class="hljs-attr">data</span>: candlestick,      <span class="hljs-comment">//data gotten from the API call above</span>
      },
    ], 

<span class="hljs-attr">yAxis</span>: [
      {
        <span class="hljs-attr">height</span>: <span class="hljs-string">"100%"</span>,       <span class="hljs-comment">// height of the candlestick chart</span>
        <span class="hljs-attr">visible</span>: <span class="hljs-literal">true</span>,  
      }
    ]
  });
});
</code></pre>
<p>The above code gives us a simple candlestick with basic styling provided by Highcharts.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8penwufkcu2l3oyg93mj.png" alt="Unstyled candlestick chart" /></p>
<h3 id="heading-stock-tools">Stock Tools</h3>
<p>The Highcharts Stock Tools is an optional feature in Highcharts. You can either enable the entire Stock Tools Graphical User Interface(GUI), which allows users to add indicators and oscillators based on their needs, or add specific Stock Tools to your web app via Javascript.</p>
<p>We will add an indicator(Acceleration bands) and an oscillator(awesome oscillator) to our chart. To do this, we need to edit both the <code>series</code> and <code>yAxis</code> objects above:</p>
<pre><code class="lang-javascript">series: [
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">"candlestick"</span>,
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Ethereum"</span>,
        <span class="hljs-attr">id</span>: <span class="hljs-string">"eth"</span>,           <span class="hljs-comment">// chart id, useful when adding indicators and oscillators</span>
        <span class="hljs-attr">data</span>: data,
      },
         {
        <span class="hljs-attr">type</span>: <span class="hljs-string">"abands"</span>,      <span class="hljs-comment">//acceleration bands indicator</span>
        <span class="hljs-attr">id</span>: <span class="hljs-string">"overlay"</span>,       <span class="hljs-comment">// overlays use the same scale and are plotted on the same axes as the main series.</span>
        <span class="hljs-attr">linkedTo</span>: <span class="hljs-string">"eth"</span>,    <span class="hljs-comment">//targets the id of the data series that it points to</span>
        <span class="hljs-attr">yAxis</span>: <span class="hljs-number">0</span>,           <span class="hljs-comment">// the index of yAxis the particular series connects to</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">"ao"</span>,          <span class="hljs-comment">// awesome oscillator</span>
        <span class="hljs-attr">id</span>: <span class="hljs-string">"oscillator"</span>,    <span class="hljs-comment">// oscillators requires additional yAxis be created due to different data extremes.</span>
        <span class="hljs-attr">linkedTo</span>: <span class="hljs-string">"eth"</span>,    <span class="hljs-comment">//targets the id of the data series that it points to</span>
        <span class="hljs-attr">yAxis</span>: <span class="hljs-number">1</span>,           <span class="hljs-comment">// the index of yAxis the particular series connects to</span>
      },
    ],
    <span class="hljs-attr">yAxis</span>: [
      {
        <span class="hljs-comment">//index 0</span>
        <span class="hljs-attr">height</span>: <span class="hljs-string">"80%"</span>,      <span class="hljs-comment">//height of main series 80%</span>

        <span class="hljs-attr">resize</span>: {
          <span class="hljs-attr">enabled</span>: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// allow resize of chart heights</span>
        },
      },
      {
        <span class="hljs-comment">//index 1</span>
        <span class="hljs-attr">top</span>: <span class="hljs-string">"80%"</span>,         <span class="hljs-comment">// oscillator series to begin at 80%</span>
        <span class="hljs-attr">height</span>: <span class="hljs-string">"20%"</span>,      <span class="hljs-comment">//height of oscillator series</span>
      },
    ],
</code></pre>
<p>Here is what we have now:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l65b6ta7q6m0x97tql23.png" alt="Unstyled candlestick chart with technical indicators" /></p>
<h2 id="heading-styling-the-chart">Styling the Chart</h2>
<p>Before we can begin styling the chart, we need to understand first the different parts that make up the chart.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5mlakarsaw0ees7bcbo.png" alt="Guideline for styling chart" /></p>
<p>Highcharts provides two ways of styling charts:</p>
<ul>
<li><p><code>Highcharts.CSSObject</code>: This is the default method of styling charts. It builds upon the <code>options</code> object within the <code>stockChart</code> class provided by Highcharts to define the visual appearance of individual SVG elements and HTML elements within the chart.</p>
</li>
<li><p><code>styledMode: boolean</code>: This defaults to <code>false</code>. However, when in styled mode, no presentational attributes are applied to the SVG via the <code>options</code> object. Hence, CSS rules are required to style the chart.</p>
</li>
</ul>
<p>We will be making use of the Highcharts default styling for this article. Therefore, within the <code>options</code> object:</p>
<pre><code class="lang-javascript">{
     <span class="hljs-comment">// general chart styles</span>
     <span class="hljs-attr">chart</span>: {
      <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#1c1b2b"</span>,       
      <span class="hljs-attr">borderRadius</span>: <span class="hljs-number">15</span>,
      <span class="hljs-attr">height</span>: <span class="hljs-number">500</span>,
      <span class="hljs-attr">styledMode</span>: <span class="hljs-literal">false</span>,            <span class="hljs-comment">//if true, CSS rules required for styles</span>
    },

    <span class="hljs-comment">// config styles for the title</span>
    <span class="hljs-attr">title</span>: {
      <span class="hljs-attr">text</span>: <span class="hljs-string">"Candlestick &amp; Awesome Oscillator"</span>, 
      <span class="hljs-attr">style</span>: {
        <span class="hljs-attr">color</span>: <span class="hljs-string">"#fff"</span>,
      },
    },

    <span class="hljs-comment">// styles for range selectors</span>
    <span class="hljs-attr">rangeSelector</span>: {
       <span class="hljs-comment">// styles for range selector buttons</span>
       <span class="hljs-attr">buttons</span>: [ 
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"month"</span>,
          <span class="hljs-attr">count</span>: <span class="hljs-number">1</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"1m"</span>,
          <span class="hljs-attr">title</span>: <span class="hljs-string">"View 1 month"</span>,
        },
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"month"</span>,
          <span class="hljs-attr">count</span>: <span class="hljs-number">4</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"4m"</span>,
          <span class="hljs-attr">title</span>: <span class="hljs-string">"View 4 months"</span>,
        },
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"month"</span>,
          <span class="hljs-attr">count</span>: <span class="hljs-number">8</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"8m"</span>,
          <span class="hljs-attr">title</span>: <span class="hljs-string">"View 8 months"</span>,
        },
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"ytd"</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"YTD"</span>,
          <span class="hljs-attr">title</span>: <span class="hljs-string">"View year to date"</span>,
        },
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"all"</span>,
          <span class="hljs-attr">count</span>: <span class="hljs-number">1</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"All"</span>,
          <span class="hljs-attr">title</span>: <span class="hljs-string">"View All"</span>,
        },
      ],
     <span class="hljs-attr">selected</span>: <span class="hljs-number">2</span>,            <span class="hljs-comment">//The index of the button to appear pre-selected on page load.</span>
      <span class="hljs-attr">buttonTheme</span>: {
        <span class="hljs-attr">fill</span>: <span class="hljs-string">"none"</span>,
        <span class="hljs-attr">stroke</span>: <span class="hljs-string">"none"</span>,
        <span class="hljs-string">"stroke-width"</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">r</span>: <span class="hljs-number">8</span>,
        <span class="hljs-attr">style</span>: {
          <span class="hljs-attr">color</span>: <span class="hljs-string">"#4F6C89"</span>,
          <span class="hljs-attr">fontWeight</span>: <span class="hljs-string">"bold"</span>,
        },

        <span class="hljs-attr">states</span>: {
        <span class="hljs-comment">// styles for different button states; hover, active etc</span>
          <span class="hljs-attr">select</span>: {
            <span class="hljs-attr">fill</span>: <span class="hljs-string">"transparent"</span>,
            <span class="hljs-attr">style</span>: {
              <span class="hljs-attr">color</span>: <span class="hljs-string">"#D76F2A"</span>,
            },
          },
        },
      },

       <span class="hljs-comment">// styles for range selector input</span>
      <span class="hljs-attr">inputBoxBorderColor</span>: <span class="hljs-string">"#4F6C89"</span>,
      <span class="hljs-attr">inputBoxWidth</span>: <span class="hljs-number">110</span>,
      <span class="hljs-attr">inputBoxHeight</span>: <span class="hljs-number">18</span>,
      <span class="hljs-attr">inputStyle</span>: {
        <span class="hljs-attr">color</span>: <span class="hljs-string">"#4F6C89"</span>,
        <span class="hljs-attr">fontWeight</span>: <span class="hljs-string">"bold"</span>,
      },
      <span class="hljs-attr">labelStyle</span>: {
        <span class="hljs-attr">color</span>: <span class="hljs-string">"#cbd1d6"</span>,
        <span class="hljs-attr">fontWeight</span>: <span class="hljs-string">"bold"</span>,
      },
    },

    <span class="hljs-comment">// The plotOptions is a wrapper object for config objects for each series type. Main structure/ styling can be defined here</span>
    <span class="hljs-attr">plotOptions</span>: {
      <span class="hljs-attr">series</span>: {
        <span class="hljs-attr">marker</span>: {
          <span class="hljs-attr">enabled</span>: <span class="hljs-literal">false</span>,         <span class="hljs-comment">//disables point markers on chart</span>
        },
      },

      <span class="hljs-comment">// config style for candlestick series</span>
      <span class="hljs-attr">candlestick</span>: {                  
        <span class="hljs-attr">lineColor</span>: <span class="hljs-string">"#FB1809"</span>,       <span class="hljs-comment">// The line color of the candlestick.</span>
        <span class="hljs-attr">color</span>: <span class="hljs-string">"#FB1809"</span>,            <span class="hljs-comment">// The default fill color candlestick. Can be used for falling candlesticks</span>
        <span class="hljs-attr">upColor</span>: <span class="hljs-string">"#4EA64A"</span>,            <span class="hljs-comment">//The fill color of the candlestick when values are rising.</span>
        <span class="hljs-attr">upLineColor</span>: <span class="hljs-string">"#4EA64A"</span>,        <span class="hljs-comment">// The line color for rising candlesticks.</span>
      },

     <span class="hljs-comment">// config style for acceleration bands indicator</span>
      <span class="hljs-attr">abands</span>: {
        <span class="hljs-attr">lineWidth</span>: <span class="hljs-number">1</span>,
        <span class="hljs-attr">lineColor</span>: <span class="hljs-string">"#20a0b1"</span>,
        <span class="hljs-attr">bottomLine</span>: {
          <span class="hljs-attr">styles</span>: {
            <span class="hljs-attr">lineWidth</span>: <span class="hljs-number">0.5</span>,
            <span class="hljs-attr">lineColor</span>: <span class="hljs-string">"#fcfc27"</span>,
          },
        },
        <span class="hljs-attr">topLine</span>: {
          <span class="hljs-attr">styles</span>: {
            <span class="hljs-attr">lineWidth</span>: <span class="hljs-number">0.5</span>,
            <span class="hljs-attr">lineColor</span>: <span class="hljs-string">"#2efc27"</span>,
          },
        },
      },
    },

   <span class="hljs-comment">// config styles for xAxis</span>
    <span class="hljs-attr">xAxis</span>: {
      <span class="hljs-attr">lineWidth</span>: <span class="hljs-number">0.1</span>,
      <span class="hljs-attr">tickColor</span>: <span class="hljs-string">"#2f2952"</span>,
      <span class="hljs-attr">crosshair</span>: {
        <span class="hljs-attr">color</span>: <span class="hljs-string">"#8e8aac"</span>,
        <span class="hljs-attr">dashStyle</span>: <span class="hljs-string">"dash"</span>,
      },
    },

   <span class="hljs-comment">// styles and config for yAxis</span>
    <span class="hljs-attr">yAxis</span>: [
      {
        <span class="hljs-attr">gridLineColor</span>: <span class="hljs-string">"#201d3a"</span>,
        <span class="hljs-attr">lineWidth</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">visible</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">height</span>: <span class="hljs-string">"80%"</span>,
        <span class="hljs-attr">crosshair</span>: {
          <span class="hljs-attr">dashStyle</span>: <span class="hljs-string">"dash"</span>,
          <span class="hljs-attr">snap</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">color</span>: <span class="hljs-string">"#696777"</span>,
        },
        <span class="hljs-attr">labels</span>: {
          <span class="hljs-attr">align</span>: <span class="hljs-string">"right"</span>,
          <span class="hljs-attr">x</span>: <span class="hljs-number">-2</span>,
        },
        <span class="hljs-attr">resize</span>: {
          <span class="hljs-attr">enabled</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">lineWidth</span>: <span class="hljs-number">2</span>,
          <span class="hljs-attr">lineColor</span>: <span class="hljs-string">"#1d1c30"</span>,
        },
      },

      {
        <span class="hljs-attr">top</span>: <span class="hljs-string">"80%"</span>,
        <span class="hljs-attr">height</span>: <span class="hljs-string">"20%"</span>,
        <span class="hljs-attr">gridLineColor</span>: <span class="hljs-string">"#201d3a"</span>,
      },
    ],

   <span class="hljs-comment">// styles and config for tooltip</span>
    <span class="hljs-attr">tooltip</span>: {
      <span class="hljs-attr">split</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">shape</span>: <span class="hljs-string">"rect"</span>,
      <span class="hljs-attr">shadow</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">valueDecimals</span>: <span class="hljs-number">2</span>,

      <span class="hljs-comment">// position tooltip</span>
      <span class="hljs-attr">positioner</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">width, height, point</span>) </span>{
        <span class="hljs-keyword">var</span> chart = <span class="hljs-built_in">this</span>.chart,
          position;

        <span class="hljs-keyword">if</span> (point.isHeader) {
          position = {
            <span class="hljs-attr">x</span>: <span class="hljs-built_in">Math</span>.max(
              <span class="hljs-comment">// Left side limit</span>
              <span class="hljs-number">0</span>,
              <span class="hljs-built_in">Math</span>.min(
                point.plotX + chart.plotLeft - width / <span class="hljs-number">2</span>,
                <span class="hljs-comment">// Right side limit</span>
                chart.chartWidth - width - chart.marginRight
              )
            ),
            <span class="hljs-attr">y</span>: point.plotY,
          };
        } <span class="hljs-keyword">else</span> {
          position = {
            <span class="hljs-attr">x</span>: point.series.chart.plotLeft,
            <span class="hljs-attr">y</span>: point.series.yAxis.top - chart.plotTop,
          };
        }

        <span class="hljs-keyword">return</span> position;
      },
    },

    <span class="hljs-comment">// disable stocktools GUI</span>
    <span class="hljs-attr">stockTools</span>: {
      <span class="hljs-attr">gui</span>: {
        <span class="hljs-attr">enabled</span>: <span class="hljs-literal">false</span>,
      },
    },

    <span class="hljs-comment">// config styles for navigator</span>
    <span class="hljs-attr">navigator</span>: {
      <span class="hljs-attr">enabled</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">height</span>: <span class="hljs-number">50</span>,
      <span class="hljs-attr">margin</span>: <span class="hljs-number">10</span>,
      <span class="hljs-attr">outlineColor</span>: <span class="hljs-string">"#8380a5"</span>,
      <span class="hljs-attr">handles</span>: {
        <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#8380a5"</span>,
        <span class="hljs-attr">borderColor</span>: <span class="hljs-string">"#e9d5d5"</span>,
      },
      <span class="hljs-attr">xAxis</span>: {
        <span class="hljs-attr">gridLineColor</span>: <span class="hljs-string">"#8380a5"</span>,
      },

    },

   <span class="hljs-comment">// config styles for scrollbar</span>
    <span class="hljs-attr">scrollbar</span>: {
      <span class="hljs-attr">barBackgroundColor</span>: <span class="hljs-string">"#8380a5"</span>,
      <span class="hljs-attr">barBorderColor</span>: <span class="hljs-string">"#8380a5"</span>,
      <span class="hljs-attr">barBorderRadius</span>: <span class="hljs-number">8</span>,
      <span class="hljs-attr">buttonArrowColor</span>: <span class="hljs-string">"#fff"</span>,
      <span class="hljs-attr">buttonBackgroundColor</span>: <span class="hljs-string">"#405466"</span>,
      <span class="hljs-attr">rifleColor</span>: <span class="hljs-string">"#fff"</span>,
      <span class="hljs-attr">trackBackgroundColor</span>: <span class="hljs-string">"#e9d5d5"</span>,
    },

    <span class="hljs-comment">// disable credits</span>
    <span class="hljs-attr">credits</span>: {
      <span class="hljs-attr">enabled</span>: <span class="hljs-literal">false</span>,
    },
  }
</code></pre>
<p>This is what our final chart becomes:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xv0b7s4h5sf6y8jflei2.png" alt="Styled Candlestick chart" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating styled cryptocurrency candlesticks with Highcharts allows you to transform raw data into visually compelling and actionable insights. By leveraging Highcharts’ flexibility, you can customize candlestick charts to align with your branding, enhance user experience, and effectively communicate market trends. Whether you're building a financial dashboard or enhancing a trading platform, the ability to design and implement tailored visualizations is a critical skill in today’s data-driven landscape.</p>
<p>With the steps outlined in this guide, you now have a foundation for working with Highcharts to create dynamic candlestick charts. Explore additional customizations and experiment with Highcharts’ extensive API to bring your cryptocurrency visualizations to the next level.</p>
]]></content:encoded></item><item><title><![CDATA[An Easy Way to Eliminate CORS Policy Errors on a Frontend Application]]></title><description><![CDATA[You just signed up for an API service and have been provided with your unique API key to work with. You've tested calls to the endpoints with Postman or VSCode's Thunder Client extension, and everything seems to work fine. Now, it's time to incorpora...]]></description><link>https://blog.dhera.dev/an-easy-way-to-eliminate-cors-policy-errors-on-a-frontend-application</link><guid isPermaLink="true">https://blog.dhera.dev/an-easy-way-to-eliminate-cors-policy-errors-on-a-frontend-application</guid><category><![CDATA[CORS]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Mon, 10 Oct 2022 10:47:13 GMT</pubDate><content:encoded><![CDATA[<p>You just signed up for an API service and have been provided with your unique API key to work with. You've tested calls to the endpoints with Postman or VSCode's Thunder Client extension, and everything seems to work fine. Now, it's time to incorporate this in your frontend application, and you get hit with an unexpected error from your browser console: 
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bury44eq94l55bh1bsm9.png" alt="CORS error message" /></p>
<p>This error on the browser can be a real pain when working with APIs, especially for a new developer. This article provides a clear understanding of what CORS policy errors are and shows ways to avoid them when developing applications. </p>
<hr />
<h2 id="heading-understanding-the-error">Understanding the error</h2>
<p>Cross-Origin Resource Sharing (CORS) is a mechanism that allows a server to control which domains (or origins) outside its own from which a browser may allow resource requests. This mechanism is implemented in line with the <em>same-origin policy</em> — a security model whereby a browser can permit scripts in a document or webpage to interact with resources from another webpage only if both web pages are of the same origin. An advantage of this is that it combats malicious attacks by preventing scripts on one page from accessing sensitive information on another page and relaying this data to the attacker. </p>
<blockquote>
<p>Two URLs have the same origin if the protocol, port (if specified), and host are the same for both.</p>
</blockquote>
<p>CORS serves as a way to relax a browser's same-origin policy by allowing cross-origin requests from subdomains and trusted third parties. When a request is made from a browser to an API endpoint, the browser sends a preflight request to its server looking for permissions. The preflight request precedes the actual request to the endpoint and checks whether or not this request will be allowed by the server. It contains information about the actual request, such as HTTP headers and methods, and determines if the browser should send the actual request by assessing the response from the server.</p>
<p> A CORS policy error like the image above is returned as a response to the preflight request sent by the browser stating that one of the required HTTP request headers — <code>Access-Control-Allow-Origin</code> — is missing. So, how do we fix this?</p>
<hr />
<h2 id="heading-finding-a-solution">Finding a solution</h2>
<p>There are several ways to fix CORS policy errors encountered in an application. The quickest of these is to install a browser extension that includes the <code>Access-Control-Allow-Origin</code> option to the request header. There are extensions available in the <a target="_blank" href="https://chrome.google.com/webstore/search/CORS">Chrome Web Store</a> that allow browsers to perform seamless cross-domain requests in a web application when enabled. However, it is misleading to solve CORS errors this way because the extension merely tricks the browser into thinking the <code>Access-Control-Allow-Origin</code> header is included in the request when that is not the case. Hence, the extension only fixes the issue while enabled on <em>your</em> browser. </p>
<h3 id="heading-fixing-cors-errors-the-right-way">Fixing CORS errors the right way</h3>
<p>Since the solution described above only temporarily fixes the CORS error by including the missing request header, it is not a recommended fix because this request header remains missing on browsers without the extension, leading to the same errors. Another way to fix CORS errors is to send all requests to the endpoint through a <em>proxy server</em>. The proxy acts as an intermediary server between the client and the endpoint server and includes the <code>Access-Control-Allow-Origin</code> header on all requests to the endpoint. When a request is made from a browser to the API endpoint, the browser immediately sends the request to the proxy server. The proxy server adds the CORS headers to the request and then forwards it to the actual endpoint server.</p>
<p>There are a few free CORS proxies available for use on the internet. However, it is not advisable to use these proxies on production applications due to security, speed, and performance concerns. A more commonly used and accepted alternative is to build an application's proxy from scratch. Building your proxy offers the same benefits as the free CORS proxies while also eliminating the security and latency concerns associated with them. In this article, you will learn how to create your own proxy server to fix CORS policy errors.</p>
<hr />
<h2 id="heading-requirements">Requirements</h2>
<p>In this section, I will show you all the steps and installations required to build this in your application. To follow along with the rest of this guide, you will need the following: </p>
<ul>
<li><a target="_blank" href="https://nodejs.org/en/download/">Node</a> and <code>npm</code> enabled on your device.</li>
<li>An API resource that restricts cross-origin HTTP requests. For this guide, I'll be using the CoinMarketCap API. </li>
<li>A frontend application with <code>npm</code> initialized.</li>
</ul>
<p>If you do not have <code>npm</code> on your project, you must initialize it by running the command below at the root of your project's directory: </p>
<pre><code class="lang-shell">npm init
</code></pre>
<p>This command creates a project scaffold with the <code>package.json</code> file that contains our project's dependencies. Next, we need to download some packages to enable us to create the proxy server and make requests from it. These packages are: </p>
<ul>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/express/">express</a>:</strong> This is a minimalistic back-end web application framework for Node.js.</li>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/cors/">cors</a>:</strong> A Node.js package that enables CORS in an Express middleware.</li>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/axios/">axios</a>:</strong> This is a promise-based HTTP client for Node.js. This is my preferred choice for making requests but feel free to use any alternative you desire. </li>
</ul>
<p>All required packages are available on the <code>npm</code> package registry. To download them to your project, run the command below at the root of your project's directory:</p>
<pre><code class="lang-shell">npm i express cors axios
</code></pre>
<hr />
<h2 id="heading-getting-started">Getting started</h2>
<p>Before we begin building our proxy, let's take a look at the current request we're making to the endpoint from our application. </p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> options = {
   <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
   <span class="hljs-attr">url</span>: <span class="hljs-string">"https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest"</span>,
   <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"X-CMC_PRO_API_KEY"</span>: process.env.REACT_APP_MARKET_CAP_KEY,
    },
   <span class="hljs-attr">params</span>: {
     <span class="hljs-attr">slug</span>: <span class="hljs-string">"bitcoin,ethereum,band-protocol,tezos"</span>,
    },
  };

  Axios.request(options)
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(response.data);
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(error);
    });
</code></pre>
<p>The snippet above makes a client-side request with JavaScript from our frontend application. Certain API providers — like ours — tend to block requests of this nature for several reasons, usually to protect their users' API keys since they can still be accessed on the browser. This request returns an error that we can view in the "Console" tab of the browser's Developer Tools.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hhfmtgcx6kipx3cdhb5c.png" alt="CORS error on browser console" /></p>
<p>Now, let's start creating our proxy server. Begin by creating a new file named <code>index.js</code> in your project's root directory. This file will contain all the Node.js code for the server. Next, paste the following code in the <code>index.js</code> file created:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> PORT = <span class="hljs-number">8080</span>;
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);
<span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">"axios"</span>);

<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();

<span class="hljs-keyword">const</span> app = express();

app.use(cors());

app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> options = {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
    <span class="hljs-attr">url</span>: <span class="hljs-string">"https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest"</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"X-CMC_PRO_API_KEY"</span>: process.env.REACT_APP_MARKET_CAP_KEY,
    },
    <span class="hljs-attr">params</span>: {
      <span class="hljs-attr">slug</span>: <span class="hljs-string">"bitcoin,ethereum,band-protocol,tezos"</span>,
    },
  };

  axios
    .request(options)
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
      res.json(response.data);
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      res.json(error);
    });
});

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is running on port <span class="hljs-subst">${PORT}</span>`</span>);
});
</code></pre>
<p>Let's go over the code above, shall we?</p>
<ul>
<li>First, we define a port for the server to run on our local machine. Ours is set to port <code>8080</code>.</li>
<li>Next, we bring in all the required modules and assign them to constants. <pre><code class="lang-javascript"><span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
</code></pre>
</li>
<li><p>The line above is required to load environment variables defined in a <code>.env</code> file. To use this in your application, you must install the <a target="_blank" href="https://www.npmjs.com/package/dotenv">dotenv</a> module as seen on the <code>npm</code> registry.</p>
</li>
<li><p>Next, we initialize the express package and assign it to a constant called <code>app</code>. Then, we enable all CORS requests by calling the <code>cors</code> package inside the <code>app.use</code> method.</p>
</li>
<li>Below that, we make an HTTP request similar to our previous client-side request inside the <code>app.get</code> method. This method lets us define a route handler for <code>GET</code> requests. Ours is set to the root route —<code>/</code>— in the snippet above.</li>
<li>Lastly, the <code>app.listen</code> method is used to bind and listen for connections on the specified port — <code>http://localhost:8080/</code>.</li>
</ul>
<p>Now that we have the server setup, we must create a start script for starting the server. Go to the <code>package.json</code> file at the root of your project directory and add the line below to the <code>scripts</code> object:</p>
<pre><code class="lang-json"><span class="hljs-string">"start:backend"</span>: <span class="hljs-string">"node index.js"</span>,
</code></pre>
<p>This defines the start script for running the server locally. Depending on your original setup, your <code>package.json</code> file should look something like this: </p>
<pre><code class="lang-json">{  
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"project_name"</span>,  
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"project_description"</span>,  
  <span class="hljs-attr">"scripts"</span>: {  
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"react-scripts start"</span>,
    <span class="hljs-attr">"start:backend"</span>: <span class="hljs-string">"node index.js"</span>
  },  
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"axios"</span>: <span class="hljs-string">"^0.21.0"</span>,
    <span class="hljs-attr">"cors"</span>: <span class="hljs-string">"^2.8.5"</span>,
    <span class="hljs-attr">"dotenv"</span>: <span class="hljs-string">"^16.0.1"</span>,
    <span class="hljs-attr">"express"</span>: <span class="hljs-string">"^4.18.1"</span>
  }
}
</code></pre>
<p>Notice all the installed packages listed as <em>dependencies</em>. Also, notice there are separate start scripts for the frontend and backend to prevent any confusion.</p>
<p>To start up the server, run the command below in your editor's terminal:</p>
<pre><code class="lang-shell">npm run start:backend
</code></pre>
<p>This command spins up the backend server at the defined port. We can view the response from the server in JSON format by visiting the port URL on a browser— <code>http://localhost:8080/</code>. </p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gnk3bniwmqngui6r191.png" alt="Response from server" /></p>
<p>Now, we can make <code>GET</code> requests to the server URL on our frontend rather than making requests to the API endpoint directly. Let's head over to our previous front-end request to implement this change:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> options = {
  <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
  <span class="hljs-attr">url</span>: <span class="hljs-string">"http://localhost:8080/"</span>,
};

Axios.request(options)
  .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(response.data);
  })
  .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(error);
  });
</code></pre>
<p>Unlike our earlier client-side request that returns an error, the above request returns the data from the API, which we can view in the browser console or use in our application.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdm6jt98cn5h77399fhz.png" alt="Result from request" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>CORS errors can be a real pain for front-end developers. However, understanding the underlying browser behavior helps us to properly visualize our approach to solving them. In this post, I briefly explained what CORS errors are, why they occur, and discussed possible ways of solving these errors. Finally, I implemented the recommended solution to fix CORS issues on a frontend application.</p>
]]></content:encoded></item><item><title><![CDATA[Authentication and Authorization in Firebase]]></title><description><![CDATA[Introduction
The importance of developing secure applications cannot be overstated these days. Users trust applications with their most sensitive data and expect it to be protected. 
When building applications, app security should be a top priority. ...]]></description><link>https://blog.dhera.dev/authentication-and-authorization-in-firebase</link><guid isPermaLink="true">https://blog.dhera.dev/authentication-and-authorization-in-firebase</guid><category><![CDATA[Firebase]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Wed, 05 Oct 2022 17:00:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742331380047/f39d4769-0d2b-46f5-a316-5ed8efd71f6a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>The importance of developing secure applications cannot be overstated these days. Users trust applications with their most sensitive data and expect it to be protected. </p>
<p>When building applications, app security should be a top priority. Developers must confirm users' identities and ensure that users can only access data they're allowed.</p>
<p>On this week's 8weeks of Firebase series, I'll cover Firebase's most fundamental offering — authentication.</p>
<h3 id="heading-what-is-firebase">What is Firebase</h3>
<p>Firebase is a Google-backed Backend-as-a-Service (BaaS) solution that provides developers with a tool kit to build, improve, and grow their applications. Backend-as-a-Service solutions like Firebase eliminate the need for developers to build and manage various back-end services and focus solely on application development. These services include authentication, file storage, databases, hosting, testing, performance monitoring, analytics, and so on.</p>
<h3 id="heading-authentication-in-firebase">Authentication in Firebase</h3>
<p>Authentication identifies and verifies users of an application. Firebase Authentication utilizes easy-to-use SDKs and ready-made UI libraries to authenticate users and create personalized experiences for users across all devices.</p>
<p>Under the hood, Firebase Authentication uses JSON Web Tokens (JWTs) to authenticate users. It supports authentication mechanisms like email/password and email/link combinations. It also allows phone number authentication and supports social logins with popular federated identity providers like Google and Github. In this tutorial, I will focus only on Google Authentication.</p>
<p>Firebase Authentication allows us to incorporate Google sign-in into our authentication workflow, allowing users to sign in with their Google accounts. To enable this on our application, we must first enable Google Authentication on the <a target="_blank" href="https://console.firebase.google.com/">Firebase project console</a>: </p>
<ul>
<li>Head to the "Authentication" section on the Firebase console.</li>
<li>Click on the "Add New Provider" button on the "Sign-in providers" panel.</li>
<li>Select "Google" from the list of native providers shown. </li>
<li>Finally, enter a project support email in the popup displayed and hit "Save".</li>
</ul>
<p>Next, we must create separate instances for Firebase auth and the Google auth provider object to utilize this sign-in method in our application. Finally, we call the <code>signInWithPopup</code> function and pass in the Firebase Auth instance as its first parameter and the Google auth provider instance as the second</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { getAuth, signInWithPopup, GoogleAuthProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;

<span class="hljs-keyword">const</span> auth = getAuth();
<span class="hljs-keyword">const</span> provider = <span class="hljs-keyword">new</span> GoogleAuthProvider();

signInWithPopup(auth, provider)
  .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
    <span class="hljs-comment">// do something</span>
  }).catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
   <span class="hljs-comment">// handle error</span>
  }
</code></pre>
<p>Once authenticated, we can listen for users' authentication states in real-time and also make requests to other Firebase services with users' unique IDs. </p>
<h3 id="heading-authorization-in-firebase">Authorization in Firebase</h3>
<p>Upon validating the identity of the user accessing resources on our application, it is also necessary to control what operations they can perform. Authorization is the process of specifying the users' privileges and controlling what resources they access. Firebase secures and controls access to every backend service using <em>security rules</em>.</p>
<p>Firebase Security Rules stand between your data and users, ensuring that your application's users can only read and write data they're allowed. Firebase Security Rules allow us to use an expression language to determine how an end user is allowed to access data in Firebase products. It works by matching a pattern against database or bucket paths and then applying custom conditions to allow read and write access to data at those paths. </p>
<p>Using Firebase Authentication, we can access users' authentication information and write security rules based on this. For example, the rules below define read and write operations for a path, <code>users/&lt;uid&gt;</code>, where the <code>uid</code> subpath could be the unique identifier of any user on our application:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"rules"</span>: {
    <span class="hljs-attr">"users"</span>: {
      <span class="hljs-attr">"$uid"</span>: {
        <span class="hljs-attr">".read"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">".write"</span>: <span class="hljs-string">"$uid === auth.uid"</span>
      }
    }
  }
}
</code></pre>
<p>In the above, we allow all read operations to the <code>users/&lt;uid&gt;</code> path, but grant write access only to users whose <code>uid</code> matches the subpath value.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Authentication and authorization are essential components of any secure application. It is necessary to implement good authentication and authorization strategies to protect your application's users and their data from attackers. Firebase provides mechanisms that protect all backend services, making sure that only users can see their own data. In this post, I briefly discussed Firebase Authentication, demonstrated an implementation of Google sign-in, and finally, I discussed securing our application's data using Firebase Security Rules.</p>
<hr />
<p>To learn more about authentication and authorization in Firebase, check out my <a target="_blank" href="https://www.educative.io/collection/6586453712175104/4892772506533888">Firebase course</a> on Educative.</p>
]]></content:encoded></item><item><title><![CDATA[New Course Alert]]></title><description><![CDATA[Hello all, 
I'm THRILLED to announce that my Firebase course is now live on Educative, Inc. This course took a lot of time to create, and I am extremely proud of it.
It truly was an exciting experience for me, and I thoroughly enjoyed working with th...]]></description><link>https://blog.dhera.dev/new-course-alert</link><guid isPermaLink="true">https://blog.dhera.dev/new-course-alert</guid><category><![CDATA[Firebase]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Wed, 28 Sep 2022 13:43:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664372459855/YBDTOOqPa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello all, </p>
<p>I'm THRILLED to announce that my Firebase course is now live on Educative, Inc. This course took a lot of time to create, and I am extremely proud of it.</p>
<p>It truly was an exciting experience for me, and I thoroughly enjoyed working with the team at Educative Inc to create this course. It was also a huge learning opportunity for me as I found myself looking through Docker and tmux documentation multiple times.</p>
<p>The course is tailored for beginner-intermediate Firebase developers and contains all you need to know to get started with Firebase. It comprehensively discusses Firebase services and demonstrates the usage and implementation of these services in a real-world application using Educative's in-app SPA widget. </p>
<p>Kindly like, share, and recommend it to anyone who wants to learn Firebase! </p>
<p><a href="https://www.educative.io/collection/6586453712175104/4892772506533888">Link to the course</a></p>
]]></content:encoded></item><item><title><![CDATA[Highcharts: Styled Heikin Ashi with Bollinger Bands]]></title><description><![CDATA[https://codepen.io/chideraao/pen/XWaYJar
 
Beautifully styled Heikin Ashi with Bollinger Bands.
Check out the full implementation on Codepen.]]></description><link>https://blog.dhera.dev/highcharts-styled-heikin-ashi-with-bollinger-bands</link><guid isPermaLink="true">https://blog.dhera.dev/highcharts-styled-heikin-ashi-with-bollinger-bands</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[webdev]]></category><category><![CDATA[data visualization]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Tue, 07 Dec 2021 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742345860178/bdb746e4-3f01-4634-bff5-f90ba4a6b5a5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/chideraao/pen/XWaYJar">https://codepen.io/chideraao/pen/XWaYJar</a></div>
<p> </p>
<p>Beautifully styled Heikin Ashi with Bollinger Bands.</p>
<p>Check out the <a target="_blank" href="https://codepen.io/chideraao/pen/XWaYJar">full implementation on Codepen</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Highcharts: Styled Candlestick vs Heikin Ashi]]></title><description><![CDATA[https://codepen.io/chideraao/pen/mdMKdQP
 
This visualization explores the difference between a traditional OHLC candlestick chart and a Heikin Ashi chart.
Check out the full implementation on Codepen.]]></description><link>https://blog.dhera.dev/highcharts-styled-candlestick-vs-heikin-ashi</link><guid isPermaLink="true">https://blog.dhera.dev/highcharts-styled-candlestick-vs-heikin-ashi</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[webdev]]></category><category><![CDATA[data visualization]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Thu, 02 Dec 2021 11:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742346170330/9857f1e9-4c6b-4566-ad11-8562b766ec7b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/chideraao/pen/mdMKdQP">https://codepen.io/chideraao/pen/mdMKdQP</a></div>
<p> </p>
<p>This visualization explores the difference between a traditional OHLC candlestick chart and a Heikin Ashi chart.</p>
<p>Check out the <a target="_blank" href="https://codepen.io/chideraao/pen/mdMKdQP">full implementation on Codepen</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Sending Emails From Your Firebase App with Nodemailer using Gmail as SMTP]]></title><description><![CDATA[I was recently working on a Firebase side project where I needed to implement a feature that sends emails to users upon signing up, and I was astonished by how difficult it was to find the resources needed to aid the implementation. I then decided to...]]></description><link>https://blog.dhera.dev/sending-emails-from-your-firebase-app-with-nodemailer-using-gmail-as-smtp</link><guid isPermaLink="true">https://blog.dhera.dev/sending-emails-from-your-firebase-app-with-nodemailer-using-gmail-as-smtp</guid><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Thu, 11 Nov 2021 20:22:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742331460881/2eca5b43-9d55-4569-9e64-7e23ad9eb7b7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was recently working on a Firebase <a target="_blank" href="https://dhera-gram.web.app/">side project</a> where I needed to implement a feature that sends emails to users upon signing up, and I was astonished by how difficult it was to find the resources needed to aid the implementation. I then decided to document the steps I took for posterity's sake. If you are looking for a guide on how to integrate Nodemailer with your Firebase application, then this is the right one for you.</p>
<h3 id="heading-what-is-firebase">What is Firebase?</h3>
<p>If you've been following the world of software development for a while now, you will no doubt, have heard of Firebase. <a target="_blank" href="https://firebase.google.com/">Firebase</a> is a Google-backed Backend-as-a-Service(BAAS) app development platform that provides hosted backend services such as authentication, hosting, storage and database solutions. You can describe it as an all-in-one backend solution for your apps.</p>
<h3 id="heading-what-is-nodemailer">What is Nodemailer?</h3>
<p><a target="_blank" href="https://nodemailer.com/about/">Nodemailer</a> is arguably the most popular emailing package for NodeJS. </p>
<blockquote>
<p>Nodemailer is a module for Node.js applications to allow easy-as-cake email sending. 
It is very easy to settle and integrate into an existing project.</p>
</blockquote>
<p>In this article, I'll show you how to integrate Nodemailer into your Firebase project with Cloud Functions. We will be setting up a 3-Legged authentication with OAuth and Gmail to obtain the access tokens needed by Nodemailer and then set those tokens as environment variables for use in our Firebase app and emulator.</p>
<h3 id="heading-requirements">Requirements</h3>
<ul>
<li><a target="_blank" href="https://nodejs.org/en/download/">NPM or NodeJS</a>.</li>
<li>A <a target="_blank" href="https://accounts.google.com/SignUp?hl=en">Google</a> account (who doesn't have one these days?).</li>
<li>Text editor of your choice (I use <a target="_blank" href="https://code.visualstudio.com/download">VSCode</a> personally).</li>
</ul>
<h3 id="heading-the-setup">The Setup</h3>
<p><a target="_blank" href="https://firebase.google.com/docs/web/setup">Setting up Firebase</a> is a very easy process. All you need to do is <a target="_blank" href="https://console.firebase.google.com/">sign in to Firebase</a> with an existing Google account and then follow the steps listed below;</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/web/setup#create-firebase-project">Create a Firebase project</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/web/setup#register-app">Register your app with Firebase</a> or use an existing app on your console.</li>
<li>Add the Firebase SDKs and initialize firebase.</li>
</ul>
<p>I am working on an already created React app, so I already have a <code>package.json</code> file at the root of my project directory. This file contains all the dependencies and config needed for my application. If you don't have one already, run the following command on your terminal while in the project's root directory:</p>
<pre><code class="lang-shell">npm init
</code></pre>
<p>Next, we need to install the firebase NPM package:</p>
<pre><code class="lang-shell">npm install --save firebase
</code></pre>
<p>Lastly, we must to import the required firebase modules into your app and then initialize the project:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Firebase App (the core Firebase SDK) is always required and must be listed first</span>
<span class="hljs-keyword">import</span> firebase <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/app"</span>;


<span class="hljs-comment">// Add the Firebase products that you want to use</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"firebase/firestore"</span>;


<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-comment">// ... paste the config values given when you created the Firebase project on the console.</span>
};

<span class="hljs-comment">// Initialize Firebase</span>
firebase.initializeApp(firebaseConfig);
</code></pre>
<p>For this article, we will be requiring the <a target="_blank" href="https://firebase.google.com/docs/cli#install_the_firebase_cli">Firebase CLI</a> for our <a target="_blank" href="https://firebase.google.com/docs/functions">Cloud Functions</a> and <a target="_blank" href="https://firebase.google.com/docs/emulator-suite">Firebase Emulators</a> setup. The Firebase CLI provides us with a set of tools to administer and manage our Firebase projects directly from the terminal. You can read more about that on the <a target="_blank" href="https://firebase.google.com/docs/cli">Firebase documentation</a>.</p>
<p>Before we carry on with the rest of this guide, we must first initialize Cloud Functions for Firebase. This provides us with a Node.js runtime environment required to run Nodemailer. To initialize functions in your Firebase project, run the command below in the root of your project's directory and follow the prompts in line with your project's specifications:</p>
<pre><code class="lang-shell">firebase init functions
</code></pre>
<p>This command creates a <code>functions</code> directory in the root of our project that holds files and modules necessary to write and deploy Cloud Functions successfully.</p>
<h3 id="heading-setting-up-a-google-cloud-project">Setting up a Google Cloud Project</h3>
<p>The first step in our tasks is to set up a Google Cloud Project.</p>
<ul>
<li>Head over to your <a target="_blank" href="https://console.cloud.google.com/">Google Developer Console</a> page. </li>
<li>On your dashboard, click on the dropdown icon on the menu. This opens up a pop-up window.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhkrvsu46un4c6jan7v6.png" alt="Alt Text" /></p>
<p>You can either use an existing project or create a new one. Since we already created a project on the Firebase console, we can access it by typing the name we gave to the project on the search bar.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a3b1drxracka39c2njm3.png" alt="Alt Text" /></p>
<h3 id="heading-getting-oauth-credentials">Getting OAuth Credentials</h3>
<p>Next, we need to retrieve our project's OAuth credentials from the Google Cloud Platform page.
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8darh21zfsy74zi5eepj.png" alt="Alt Text" /></p>
<ul>
<li>On the sidebar of the developer console, click the "APIs and services" menu.</li>
<li>Then, click the "Credentials" option to go to the "Credentials" page. </li>
</ul>
<p>On the "Credentials" page, you will notice that we already have an OAuth 2.0 Client ID autogenerated for us by Google client. This was created when we created our Firebase project on the console. </p>
<ul>
<li>Click on the "Web Client (auto-created by Google Service)" link to show the credentials. </li>
<li>Then, copy the Client ID and the Client secret from the list of credentials. These are required to set up the OAuth configuration.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aw25jnl2hv487nvsssc3.png" alt="Alt Text" />
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z4juwpshg8gktuif2r49.png" alt="Alt Text" /></p>
<h3 id="heading-getting-oauth-tokens">Getting OAuth Tokens</h3>
<p>The easiest way to get the required OAuth tokens is to use the OAuth 2.0 playground.</p>
<ul>
<li>Head over to the <a target="_blank" href="https://developers.google.com/oauthplayground">OAuth 2.0 playground</a> page.</li>
<li>Click on the cog icon (⚙️) at the top right corner of the screen to display the interaction window. Then check the "Use your OAuth credentials" option. </li>
<li>Next, paste the Client secret and Client ID gotten from the Google Cloud Platform's "Credentials" page.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5kdmafeztr2bmqtj5394.png" alt="Alt Text" /></p>
<p>Now, we need to set the scope of the OAuth credentials by authorizing the Gmail API for our project:</p>
<ul>
<li>Head over to the "Select and authorize APIs" section on the left side of the screen. </li>
<li>Next, paste the Gmail link — <code>https://mail.google.com</code>— into the text field provided to authorize the Gmail API.</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnrqeegvscdwmc95fde2.png" alt="Alt Text" /></p>
<p>Then, click on the "Authorize APIs" button. This opens up a Google authentication prompt. 
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56vfb8oi909xf95p8txn.png" alt="Alt Text" /></p>
<p>Select the Google account in use and then authorize your app to access your Google account. 
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d20cgz6a89kmxwf7yfbi.png" alt="Alt Text" />
Click on the "Advanced" button at the bottom.
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9wqmzatetirb9l6cmju.png" alt="Alt Text" />
Click on the "Continue to project" button at the bottom and then grant the app access to your Google account.
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbjqrvki1yqrt43w0d12.png" alt="Alt Text" /></p>
<p>After completing the steps above, you will be redirected back to the OAuth playground. </p>
<ul>
<li>Click on the "Exchange authorization code for tokens" button on the left-hand side of the screen.
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7drpzp3txjbwqof7ck6x.png" alt="Alt Text" /></li>
</ul>
<p>When the request completes, it will return an object on the "Response/Request" section of the screen that contains your <em>access token</em> and <em>refresh token</em>.
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8btcmsdgwjdmvqviej37.png" alt="Alt Text" /></p>
<p>These values, along with the Client Secret and Client ID from the credentials page, make up our OAuth credentials needed for Nodemailer.</p>
<h3 id="heading-firebase-environment-variables">Firebase Environment Variables</h3>
<p>You will often need to set up additional environment configurations for your Firebase Functions. These can be third-party API keys, sensitive data, or, in our case, our OAuth credentials. The Firebase SDK for Cloud Functions offers a built-in environment configuration to make it easy to store and retrieve this type of data for your project.</p>
<p>Setting of environment variables in Firebase is done with the command </p>
<pre><code class="lang-shell">firebase functions:config:set x.key="THE API KEY" x.id="THE CLIENT ID"
</code></pre>
<p>For this project, we need to set environment variables to store our access and refresh tokens; and our client secret and client ID.</p>
<p>We can do this by running the command in out terminal:</p>
<pre><code class="lang-shell">firebase functions:config:set gmail.useremail="yourgoogleemail@gmail.com" gmail.clientid="yourclientid.apps.googleusercontent.com" gmail.refreshtoken="1//04zKnDTh1mXdLCgYI-yourrefreshtoken" gmail.clientsecret="mbFQnYOurCLienTSecREt"
</code></pre>
<blockquote>
<p><em>Keep in mind that only lowercase characters are accepted as keys.</em> </p>
</blockquote>
<p>If your project is running with a Firebase emulator, you must retrieve your custom configuration variables to make them locally accessible. Depending on your operating system, run either of the following commands in the <code>functions</code> directory of your project:</p>
<p>For MacOS:</p>
<pre><code class="lang-shell">firebase functions:config:get &gt; .runtimeconfig.json
</code></pre>
<p>And for Windows</p>
<pre><code class="lang-shell">firebase functions:config:get | ac .runtimeconfig.json
</code></pre>
<h3 id="heading-accessing-environment-variables-on-firebase">Accessing environment variables on Firebase</h3>
<p>In Firebase, defined environment variables are made available to functions via <code>functions.config()</code>. We can access them within our application by following the following syntax:</p>
<pre><code class="lang-javascript">functions.config().envKey.envValue
</code></pre>
<p>We can destructure these values in our <code>index.js</code> file to make it more readable:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> { useremail, refreshtoken, clientid, clientsecret } = functions.config().gmail;
</code></pre>
<h3 id="heading-installing-and-configuring-nodemailer">Installing and Configuring Nodemailer</h3>
<p>For this part of the tutorial, you will need to install Nodemailer if you haven't already. To install Nodemailer, run the code below on the terminal within your project's <code>functions</code> directory :</p>
<pre><code class="lang-shell">npm install nodemailer
</code></pre>
<p>Then, copy the code below and paste in your <code>index.js</code> file within your <code>functions</code> folder:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> functions = <span class="hljs-built_in">require</span>(<span class="hljs-string">"firebase-functions"</span>);
<span class="hljs-keyword">const</span> admin = <span class="hljs-built_in">require</span>(<span class="hljs-string">"firebase-admin"</span>);
<span class="hljs-keyword">const</span> nodemailer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"nodemailer"</span>);

admin.initializeApp();

<span class="hljs-comment">/** defining and destructuring environments config for firebase functions */</span>
<span class="hljs-keyword">let</span> { useremail, refreshtoken, clientid, clientsecret } =
    functions.config().gmail;

<span class="hljs-comment">/**create reusable transporter object using the gmail SMTP transport */</span>
<span class="hljs-keyword">let</span> transporter = nodemailer.createTransport({
    <span class="hljs-attr">host</span>: <span class="hljs-string">"smtp.gmail.com"</span>,
    <span class="hljs-attr">port</span>: <span class="hljs-number">465</span>,
    <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">auth</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">"OAuth2"</span>,
        <span class="hljs-attr">user</span>: useremail,
        <span class="hljs-attr">clientId</span>: clientid,
        <span class="hljs-attr">clientSecret</span>: clientsecret,
        <span class="hljs-attr">refreshToken</span>: refreshtoken,
    },
});


<span class="hljs-comment">//our firebase cloud function</span>
<span class="hljs-built_in">exports</span>.userCreate = functions.auth.user().onDelete(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> {

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Replace the `from`, `html` and `subject` values</span>
    <span class="hljs-keyword">const</span> mailOptions = {
        <span class="hljs-attr">from</span>: <span class="hljs-string">"okekechidera97@gmail.com"</span>,
        <span class="hljs-attr">to</span>: user.email,
        <span class="hljs-attr">subject</span>: <span class="hljs-string">"Thanks for Signing up"</span>,
        <span class="hljs-attr">html</span>: <span class="hljs-string">`&lt;div
        Hey, I am an HTML template
    &lt;/div&gt;`</span>,

    };

<span class="hljs-comment">// send mail with defined transport object</span>
<span class="hljs-keyword">return</span> transporter.sendMail(mailOptions).catch(<span class="hljs-function">(<span class="hljs-params">err</span>)=&gt;</span>{
        <span class="hljs-built_in">console</span>.log(err);
    });
});
</code></pre>
<p>The code above illustrates a Firebase Auth triggered function that uses Nodemailer to send emails to new users upon signup. Edit the <code>from</code>, <code>subject</code>, and <code>html</code> values in the <code>mailOptions</code> object to suit your needs. However, all Cloud Functions must be deployed before usage; therefore, we must deploy our newly created function. To deploy a Cloud Function, run the command below in the terminal:</p>
<pre><code class="lang-shell">firebase deploy --only functions
</code></pre>
<p>This command bundles all the Cloud Functions code contained in the <code>index.js</code> file and deploys them to the Cloud Functions runtime.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>We just discussed how to integrate and send emails with Nodemailer in a Firebase project; I hope you found it useful. Thanks for taking the time to read this. 
This article is my first attempt at technical writing; I appreciate any feedback you might have.</p>
<p>The project that inspired this article is available <a target="_blank" href="https://dhera-gram.web.app/">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Highcharts: Styled OHLC with MACD Oscillator]]></title><description><![CDATA[https://codepen.io/chideraao/pen/XWaYJjL
 
Beautifully styled OHLC chart with MACD oscillator.]]></description><link>https://blog.dhera.dev/highcharts-styled-ohlc-with-macd-oscillator</link><guid isPermaLink="true">https://blog.dhera.dev/highcharts-styled-ohlc-with-macd-oscillator</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[data visualization]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Mon, 08 Nov 2021 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742345287030/ed936522-3f02-4980-8f62-f36163915053.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/chideraao/pen/XWaYJjL">https://codepen.io/chideraao/pen/XWaYJjL</a></div>
<p> </p>
<p>Beautifully styled OHLC chart with MACD oscillator.</p>
]]></content:encoded></item><item><title><![CDATA[By Value VS By Reference: A Beginner's Perspective]]></title><description><![CDATA[One of the most important concepts of programming is the distinction between passing by value and passing by reference. In this post, I will try my best to explain the concept as I understand it as a beginner.
Passing by Value
Passing by value refers...]]></description><link>https://blog.dhera.dev/by-value-vs-by-reference-a-beginners-perspective</link><guid isPermaLink="true">https://blog.dhera.dev/by-value-vs-by-reference-a-beginners-perspective</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[newbie]]></category><dc:creator><![CDATA[Dera Okeke]]></dc:creator><pubDate>Mon, 16 Nov 2020 23:00:00 GMT</pubDate><content:encoded><![CDATA[<p>One of the most important concepts of programming is the distinction between passing by value and passing by reference. In this post, I will try my best to explain the concept as I understand it as a beginner.</p>
<h3 id="passing-by-value">Passing by Value</h3>
<p>Passing by value refers to a variable being stored as its literal value rather than some other method. Typically, in JavaScript, primitive types( numbers, strings, booleans, etc) are always stored by value. This means that altering their values within a function or after being passed to another variable doesn’t change the value of the original declaration. Take the block of code below for example;</p>
<pre><code><span class="hljs-keyword">let</span> a = <span class="hljs-number">5</span>;               <span class="hljs-comment">//the integer value of 10 is stored in variable a</span>
<span class="hljs-keyword">let</span> <span class="hljs-built_in">c</span> = “lovely article”;   <span class="hljs-comment">// the string value is stored in variable c</span>
<span class="hljs-keyword">let</span> d = a;               <span class="hljs-comment">//this stores the value of 10 in variable d</span>
</code></pre><p>Making a change to the <em>variable d</em> would not result to a change in the value stored in <em>variable a</em> as it is set to the value of 5 rather than the location <em>variable a</em> is stored. This is because in as much as the <em>variable d</em> points to the value stored in the <em>variable a</em>, it actually holds an entirely different value. </p>
<pre><code>d = d + <span class="hljs-number">5</span>;

console.<span class="hljs-built_in">log</span>(a);    <span class="hljs-comment">//logs 5</span>
console.<span class="hljs-built_in">log</span>(d);    <span class="hljs-comment">//logs 10</span>
</code></pre><h3 id="passing-by-reference">Passing by Reference</h3>
<p>The other way by which variables can be stored is by Reference. This refers to when a variable been stored at a particular memory location on the local device. Arrays and objects are typically stored by reference to a particular memory location. A change made to the variable affects all other variables which point to that memory location. For example: </p>
<pre><code><span class="hljs-keyword">let</span> f = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>];  <span class="hljs-comment">//0x00        </span>
<span class="hljs-keyword">let</span> g = f;     <span class="hljs-comment">//0x00</span>
</code></pre><p>Making a change to <em>variable g</em> also changes to the value of <em>variable f</em> because the <em>variable f</em> (and by extension <em>variable g</em> )holds a reference to the memory address(say <em>0x00</em>) rather than the array itself. The value of the array is however stored at this memory address, 0x00.</p>
<pre><code><span class="hljs-string">let</span> <span class="hljs-string">f</span> <span class="hljs-string">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]<span class="hljs-string">;</span>        <span class="hljs-string">//0x00</span>
<span class="hljs-string">let</span> <span class="hljs-string">g</span> <span class="hljs-string">=</span> <span class="hljs-string">f;</span>             <span class="hljs-string">//0x00</span>

<span class="hljs-string">g.push(5)</span>

<span class="hljs-string">console.log(f);</span>        <span class="hljs-string">//logs</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">5</span>]
<span class="hljs-string">console.log(g);</span>    <span class="hljs-string">//logs</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">5</span>]
</code></pre><p>If you however assign <em>variable g</em> to a different array of numbers, this array would be  stored in a new memory address(say 0x01). This allows you to overwrite the previous memory location set in the declaration( as in the previous block of code). The block of code below explains further:</p>
<pre><code><span class="hljs-string">let</span> <span class="hljs-string">f</span> <span class="hljs-string">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]<span class="hljs-string">;</span>    <span class="hljs-string">//0x00</span>
<span class="hljs-string">let</span> <span class="hljs-string">g</span> <span class="hljs-string">=</span> <span class="hljs-string">f;</span>          <span class="hljs-string">//0x00</span>


<span class="hljs-string">console.log(f);</span>     <span class="hljs-string">//logs</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]
<span class="hljs-string">console.log(g);</span> <span class="hljs-string">//logs</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]

<span class="hljs-string">g=</span> [<span class="hljs-number">4</span>,<span class="hljs-number">3</span>,<span class="hljs-number">2</span>,<span class="hljs-number">1</span>]<span class="hljs-string">;</span>     <span class="hljs-string">//0x01</span>

<span class="hljs-string">g.push(5)</span>

<span class="hljs-string">console.log(f);</span>     <span class="hljs-string">//logs</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]
<span class="hljs-string">console.log(g);</span> <span class="hljs-string">//logs</span> [<span class="hljs-number">4</span>,<span class="hljs-number">3</span>,<span class="hljs-number">2</span>,<span class="hljs-number">1</span>,<span class="hljs-number">5</span>]

<span class="hljs-string">let</span> <span class="hljs-string">i</span> <span class="hljs-string">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]<span class="hljs-string">;</span>    <span class="hljs-string">//0x02</span>
</code></pre><p>It’s also worth noting that checking equality on two arrays that have the same exact values but do not reference the same memory locations returns <em>false</em>. Equality checks only return <em>true</em> if both arrays are stored at the same memory location.</p>
<pre><code><span class="hljs-keyword">let</span> f = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>];        <span class="hljs-comment">//0x00</span>
<span class="hljs-keyword">let</span> g = f;          <span class="hljs-comment">//0x00</span>

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`f == g <span class="hljs-subst">${f==g}</span>`</span>);    <span class="hljs-comment">//logs f == g true </span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`f === g <span class="hljs-subst">${f===g}</span>`</span>);    <span class="hljs-comment">//logs f === g true</span>

<span class="hljs-keyword">let</span> i = [<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>];    <span class="hljs-comment">//0x01</span>
<span class="hljs-keyword">let</span> j = [<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>];    <span class="hljs-comment">//0x02</span>

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`i == j <span class="hljs-subst">${i==j}</span>`</span>);        <span class="hljs-comment">//logs f == g false</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`i === j <span class="hljs-subst">${i===j}</span>`</span>);    <span class="hljs-comment">//logs f === g false</span>
</code></pre><h3 id="conclusion">Conclusion</h3>
<p>The concepts of passing by values and passing by references in programming are important as it could lead to bugs that may be somewhat difficult to track down. Understanding this concept helps to save you time and effort when debugging your code.</p>
]]></content:encoded></item></channel></rss>