<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Duncan Lew</title>
    <description>The latest articles on Forem by Duncan Lew (@duncanlew).</description>
    <link>https://forem.com/duncanlew</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F817483%2F36e1ae0a-11b7-4a52-8b35-bd02dd605bd1.jpeg</url>
      <title>Forem: Duncan Lew</title>
      <link>https://forem.com/duncanlew</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/duncanlew"/>
    <language>en</language>
    <item>
      <title>How I Built a SpeedtestTracker with Raspberry PI and AWS Lambda</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Fri, 07 Feb 2025 21:56:59 +0000</pubDate>
      <link>https://forem.com/duncanlew/how-i-built-a-speedtesttracker-with-raspberry-pi-and-aws-lambda-5d6n</link>
      <guid>https://forem.com/duncanlew/how-i-built-a-speedtesttracker-with-raspberry-pi-and-aws-lambda-5d6n</guid>
      <description>&lt;p&gt;Our daily lives, whether we’re gaming, streaming or working from home, revolve around the internet and all the digital services that it provides. A pillar of this connection is the reliability of the download and upload speeds that your ISP can deliver. When you sign up for a service that promises certain speeds, you want to validate that you get what you pay for. This need inspired me to create SpeedtestTracker: a system that performs daily speed tests, stores the result in a database and even sends out an alert via a Telegram bot. This blog post will walk you through the steps I took to bring this idea to life. &lt;/p&gt;

&lt;h2&gt;
  
  
  1. Designing the system
&lt;/h2&gt;

&lt;p&gt;SpeedtestTracker is set up as a two part system: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client for running speedtests, extracting and saving the results&lt;/li&gt;
&lt;li&gt;the server for processing, storing and sending alerts of the data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Raspberry Pi, as the title already reveals, will act as the client and the server will be made of an AWS Lambda function. To streamline the development, I consolidated both the client-side and server-side components into a single TypeScript project. This approach enabled interface sharing, simplifying the communication between the two components. &lt;/p&gt;

&lt;p&gt;The following dependencies played an important role in the implementation: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js v22&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://axios-http.com/docs/intro" rel="noopener noreferrer"&gt;Axios&lt;/a&gt; for sending data to the server-side&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; for bunding both the client and server separately&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/motdotla/dotenv?tab=readme-ov-file#readme" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt; for loading environment variables&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/node-cron/node-cron" rel="noopener noreferrer"&gt;node-cron&lt;/a&gt; for scheduling daily speedtests on the client&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getpino.io/#/" rel="noopener noreferrer"&gt;Pino&lt;/a&gt; for structured logging&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://telegraf.js.org/" rel="noopener noreferrer"&gt;Telegraf&lt;/a&gt; as a library for interacting with Telegram bots&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ajv.js.org/" rel="noopener noreferrer"&gt;AJV&lt;/a&gt; for validating JSON responses from the speedtest cli&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this sounds like something that you would like to try out or improve, check out the souce code on &lt;a href="https://github.com/duncanlew/SpeedtestTracker" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Hardware setup with the Raspberry Pi as the client
&lt;/h2&gt;

&lt;p&gt;The backbone of the project starts with the Raspberry Pi. A very small, yet powerful and energy efficient computer that can run speedtests according to a schedule. Running frequent speedtests the whole day could congest your network, so I opted for a daily schedule instead. For my use-case, I don’t need to see deviations in my speed throughout the day. Having my Pi run a speedtest once a day fits my needs to collect data for statistical analysis over time. &lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Speedtest-cli
&lt;/h3&gt;

&lt;p&gt;The Pi is going to run a shell script that executes the &lt;a href="https://www.speedtest.net/apps/cli" rel="noopener noreferrer"&gt;Speedtest-cli&lt;/a&gt; tool to capture the Internet speed results. Here’s the command it uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;speedtest &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To automate the process, I wrote a TypeScript function that executes the shell command with error handling and parsing of the JSON result. Here’s the implementation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runSpeedtest&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speedtest --format=json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error executing command: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Standard Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parseError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to parse JSON output: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parseError&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2.2 Sending data to the endpoint
&lt;/h3&gt;

&lt;p&gt;After the JSON result is retrieved, we can send this data to an endpoint. This endpoint URL is protected by an API key and triggers an AWS Lambda when it’s called. I’m using the address in the input argument as a location identifier. Here’s the function for sending the data via a POST request:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveSpeedTestResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpeedtestResultDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpeedtestTrackerPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2.3 Scheduling a cron job
&lt;/h3&gt;

&lt;p&gt;With these methods in place, we can schedule the speedtest to run once a day at a specific time with node-cron. This is what the cron job looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 13 * * *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ADDRESS&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runSpeedtest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveSpeedTestResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error in running the cron job&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2.4 Managing the node process with PM2
&lt;/h3&gt;

&lt;p&gt;When running this script on the Raspberry Pi we want to keep the cron job alive. If you start the node process for the cron job via an SSH session, the process will terminate when you terminate the SSH session. This is where PM2 comes in. PM2 is a process manager that can run node processes in the background indefinitely.  &lt;/p&gt;

&lt;p&gt;While PM2 can run TypeScript directly, it’s not recommended in production due to inefficiency. To overcome this, we will first transpile TypeScript into JavaScript with esbuild:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;esbuild lib/speedtest-cli/index.ts &lt;span class="nt"&gt;--bundle&lt;/span&gt; &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node &lt;span class="nt"&gt;--sourcemap&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node22 &lt;span class="nt"&gt;--legal-comments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;none &lt;span class="nt"&gt;--outfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build/speedtest-cli/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run the transpiled JavaScript with PM2:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 start build/speedtest-cli/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For convenience, these commands can be added to the package.json. As long as your Raspberry Pi remains powered on, this node process will run in the background indefinitely.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. AWS Lambda as the server
&lt;/h2&gt;

&lt;p&gt;On the server side, I use an AWS Lambda function to process and store the results. For the infrastructure, project setup and the deployment, I rely on AWS CDK. &lt;/p&gt;
&lt;h3&gt;
  
  
  3.1 AWS CDK and project setup
&lt;/h3&gt;

&lt;p&gt;The AWS Cloud Development Kit, or AWS CDK, allows me to define the cloud infrastructure using a familiar programming language instead of managing a big YAML or JSON file that is required by AWS CloudFormation. In my situation I’m using TypeScript to define my resources.&lt;/p&gt;

&lt;p&gt;To initialize a CDK project, I ran the following command in my SpeedtestTracker project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk init app &lt;span class="nt"&gt;--language&lt;/span&gt; typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, I installed the &lt;code&gt;esbuild&lt;/code&gt;  dependency that will be used by the CDK to compile my TypeScript code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="nx"&gt;esbuild&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Inside the &lt;code&gt;lib&lt;/code&gt; directory, CDK generates a file called &lt;code&gt;speedtest-tracker-stack.ts&lt;/code&gt; . This acts as the scaffolding where you can define your resources. &lt;/p&gt;
&lt;h3&gt;
  
  
  3.2 Adding an AWS Lambda resource
&lt;/h3&gt;

&lt;p&gt;For my Lambda resource I added the following code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SaveSpeedtestFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda-handler/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_LATEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sourceMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--enable-source-maps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;CHAT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHAT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The lambda handler according to this snippet will be located in the lambda-handler directory and I’ve included the required environment variables that should be included for the lambda to run correctly.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.3 Adding a DynamoDB resource for data storage
&lt;/h3&gt;

&lt;p&gt;To store the results, I’ve set up a DynamoDB table. The following code snippet creates a table with the partitionKey and sortKey and grants read/write rights to the Lambda function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TableV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;sortKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;epochTime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUMBER&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speedtest-tracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantReadWriteData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3.4 Creating an securing the API Gateway
&lt;/h3&gt;

&lt;p&gt;An API Gateway serves as the interface between the client and the server. To secure the API, I added an API key and linked it to a usage plan. The usage plan limits request rates, ensuring that the API stays performant. Here’s the code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;apigw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaRestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`ApiGwEndpoint`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;restApiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SpeedtestTrackerApi`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;deployOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;stageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;defaultMethodOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;apiKeyRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addUsagePlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UsagePlan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SpeedtestTrackerUsagePlan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;burstLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ApiKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apiKeyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SpeedtestTrackerApiKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addApiStage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deploymentStage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3.5 Deploy the defined resources
&lt;/h3&gt;

&lt;p&gt;With all the resources defined, they can be synthesized and deployed with the following commands:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="nx"&gt;synth&lt;/span&gt;
&lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3.6 Lambda handler
&lt;/h3&gt;

&lt;p&gt;The lambda handler will act as the server-side part, that will process the incoming speedtest result from the Raspberry Pi, validates it using the AJV schema validation and then stores it in DynamoDB. At the end we will also send a success/failure message via Telegram using Telegraf.&lt;/p&gt;

&lt;p&gt;The core lambda handler looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;withRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speedtestResultDto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSpeedtestResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;speedtestResultDto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;telegramMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;constructSuccessMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;putResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;putItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;telegramMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;successResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;putResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error in the lambda handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="na"&gt;SpeedtestTrackerValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SpeedtestTrackerValidationError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="na"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Unknown error occurred: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringifiedMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;telegramMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;constructFailureMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stringifiedMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;telegramMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;errorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stringifiedMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;

&lt;h3&gt;
  
  
  3.7 JSON validation
&lt;/h3&gt;

&lt;p&gt;In the lambda handler, we’re using an extractPayload function that parses and validates the incoming JSON against a predefined schema. If there are missing fields in the payload, an error &lt;code&gt;SpeedtestTrackerValidationError&lt;/code&gt;  will be thrown to prevent incorrect data from being stored into the DynamoDB.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;extractPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpeedtestTrackerValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payload may not be empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsedBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpeedtestTrackerValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`JSON validation failed with error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsedBody&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SpeedtestTrackerPayload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The setup for the validate function can be found over here in [the project].(&lt;a href="https://github.com/duncanlew/SpeedtestTracker/blob/main/lib/lambda-handler/schema.ts" rel="noopener noreferrer"&gt;https://github.com/duncanlew/SpeedtestTracker/blob/main/lib/lambda-handler/schema.ts&lt;/a&gt;)&lt;/p&gt;
&lt;h3&gt;
  
  
  3.8 Save result in DynamoDB
&lt;/h3&gt;

&lt;p&gt;The lambda handler will persist the speedtest result in DynamoDB using the &lt;code&gt;putItem&lt;/code&gt; utility function. It will structure the speedtest result for querying and analysis. Each entry requires a primary key (pk) and a sort key. The pk will store the address of the home for which I’m tracking the speedtest results. The sort key will store the epoch time in seconds. Using this composite key of pk and sort key, you will be able query for all the speedtest results of a range.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;putItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpeedtestResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;epochTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toEpochSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;downloadMbps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadMbps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;uploadMbps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadMbps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pingMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;speedtestResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pingMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;docClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;

&lt;h3&gt;
  
  
  3.9 Send Telegram message
&lt;/h3&gt;

&lt;p&gt;For the telegram integration, I made use of the Telegraf dependency which requires two variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CHAT_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BOT_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get the &lt;code&gt;BOT_TOKEN&lt;/code&gt;, you will first have to create a specialized chatbot via the Telegram app. During the creation of your chatbot, a token will unique and private token will be generated. The &lt;code&gt;CHAT_ID&lt;/code&gt; is the specific chat session for which you want the chatbot to send a message.&lt;/p&gt;

&lt;p&gt;These env variables will be handled in the .env file which can be loaded with the dotenv dependency.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Telegraf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;telegraf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHAT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;botToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Telegraf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendTelegramMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Takeaway
&lt;/h2&gt;

&lt;p&gt;Building a SpeedtestTracker with a Raspberry Pi demonstrates how to turn a simple IoT device into a monitoring tool for your internet performance. We also leveraged AWS native tools like AWS CDK, AWS Lambda, and DynamoDB to process, store, and send alerts for the daily speedtest results. This project serves as an important learning tool to apply your knowledge of how to develop and utilize both cloud and hardware resources to achieve your goal. There are many small but valuable challenges that you’ll encounter like setting up deployments, validating JSON for data storage and configuring Telegram alerts. No matter whether you’re looking into learning about network monitoring or wanting to expand your skills in serverless architecture, this project is an ideal starting point. Happy coding! 🚀&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/duncanlew" rel="noopener noreferrer"&gt;
        duncanlew
      &lt;/a&gt; / &lt;a href="https://github.com/duncanlew/SpeedtestTracker" rel="noopener noreferrer"&gt;
        SpeedtestTracker
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SpeedtestTracker&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Built with Raspberry Pi and AWS Lambda&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The accompanying blog post for this project can be found on &lt;a href="https://duncanlew.medium.com/how-i-built-a-speedtesttracker-with-raspberry-pi-and-aws-lambda-19ccaf60607c" rel="nofollow noopener noreferrer"&gt;Medium&lt;/a&gt; and &lt;a href="https://dev.to/duncanlew/how-i-built-a-speedtesttracker-with-raspberry-pi-and-aws-lambda-5d6n" rel="nofollow"&gt;dev.to&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/duncanlew/SpeedtestTracker" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
    </item>
    <item>
      <title>A Guide to Parsing CSV Files in Go</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Tue, 02 Apr 2024 20:30:34 +0000</pubDate>
      <link>https://forem.com/duncanlew/a-guide-to-parsing-csv-files-in-go-4lp7</link>
      <guid>https://forem.com/duncanlew/a-guide-to-parsing-csv-files-in-go-4lp7</guid>
      <description>&lt;p&gt;Among all the programming languages available, Go, or Golang, has become more popular in the past years. In 2023 Go re-entered the top 10 spot of most popular languages, according to &lt;a href="https://www.infoworld.com/article/3689949/golang-returns-to-the-top-10.html" rel="noopener noreferrer"&gt;InfoWorld&lt;/a&gt;. This language has garnered so much clout because of its simplicity, efficiency and ability to compile directly to machine code, offering many speed benefits. &lt;/p&gt;

&lt;p&gt;As someone new to Go, I would like to dive deeper into this language to find out its potential. The best way to learn something new is by doing it. So that is why I started on a project to not only hone my Go skills, but also solve a common task: CSV parsing and data manipulation. Through this blog post I will illustrate how to parse a CSV file containing rows of articles. These articles need to be filtered based on a property and then written to a new CSV file. The accompanying source code to this project can be found on &lt;a href="https://github.com/duncanlew/demo-go-csv-parser/tree/main" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To get started with the project, you will need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://go.dev/doc/install" rel="noopener noreferrer"&gt;Go version 1.22.1&lt;/a&gt; or later&lt;/li&gt;
&lt;li&gt;IDE of choice (e.g. &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Initialize the project
&lt;/h2&gt;

&lt;p&gt;The first step in creating our Go project involves setting up a new directory called  &lt;code&gt;demo-go-csv-parser&lt;/code&gt;. Then navigate into this directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;demo-go-csv-parser
&lt;span class="nb"&gt;cd &lt;/span&gt;demo-go-csv-parser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The next step is to initialize a Go module called &lt;code&gt;demo-go-csv-parser&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go mod init demo-go-csv-parser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A &lt;code&gt;go.mod&lt;/code&gt; file will be created inside your directory. This module file is used to organize and manage dependencies, similar to the &lt;code&gt;package.json&lt;/code&gt; of the Node.js ecosystem. &lt;/p&gt;
&lt;h2&gt;
  
  
  2. Install the CSV dependency
&lt;/h2&gt;

&lt;p&gt;The dependency that we're going to use is called &lt;a href="https://github.com/gocarina/gocsv" rel="noopener noreferrer"&gt;gocsv&lt;/a&gt;. This package provides a variety of built-in functions for parsing CSVs using Go structs. To include the dependency in your project, run the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go get github.com/gocarina/gocsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Write the code
&lt;/h2&gt;

&lt;p&gt;It's time to dive into the coding aspect of the project to get a taste of how the Go programming language works. To maintain clarity, we're going to break down the coding process into the following sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create main file&lt;/li&gt;
&lt;li&gt;CSV file setup&lt;/li&gt;
&lt;li&gt;Read file&lt;/li&gt;
&lt;li&gt;Filter articles&lt;/li&gt;
&lt;li&gt;Write file
By decomposing the whole project into bite-size chunks, we can tackle each part with more attention. 
### Create main file
In the root folder of the &lt;code&gt;demo-go-csv-parser&lt;/code&gt; project, create a file called &lt;code&gt;main.go&lt;/code&gt;.
Populate the file with the following:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gocarina/gocsv"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first line indicates the package name for the file. Every Go file needs to start with a package declaration. The second part of the file consists of the import block with two dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gocsv: the external package that we've previously installed&lt;/li&gt;
&lt;li&gt;os: this built-in Go dependency will be used for I/O functionalities to read and write CSV files. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  CSV file
&lt;/h3&gt;

&lt;p&gt;For this project, we're going to use a sample CSV file that you can find at this &lt;a href="https://github.com/duncanlew/demo-go-csv-parser/blob/main/example.csv" rel="noopener noreferrer"&gt;GitHub link&lt;/a&gt;. Feel free to download and include this sample file in your project. Most CSV files are structured and have a header to denominate what each column will be used for. In Go, we can map each of CSV row into a custom data structure called &lt;code&gt;struct&lt;/code&gt;. This struct will contain fields corresponding to a CSV column.&lt;/p&gt;

&lt;p&gt;In our CSV file, the first two columns in this CSV file are named &lt;code&gt;Title&lt;/code&gt; and &lt;code&gt;URL&lt;/code&gt;. Mapping these two into a Go struct called &lt;code&gt;Article&lt;/code&gt; would look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; 
    &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The gocsv dependency supports the usage of tags to indicate what the column name is in the CSV file. This is a handy feature in cases where you would have spaces in the column name or if the column name deviates from the actual field name we would like to use in the Go struct. &lt;/p&gt;

&lt;p&gt;Considering all the columns of our CSV file, we can add all the columns with the csv tags to the final &lt;code&gt;Article&lt;/code&gt; struct which should look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;           &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"Title"`&lt;/span&gt;
    &lt;span class="n"&gt;URL&lt;/span&gt;             &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"URL"`&lt;/span&gt;
    &lt;span class="n"&gt;DocumentTags&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"Document tags"`&lt;/span&gt;
    &lt;span class="n"&gt;SavedDate&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"Saved date"`&lt;/span&gt;
    &lt;span class="n"&gt;ReadingProgress&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"Reading progress"`&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"Location"`&lt;/span&gt;
    &lt;span class="n"&gt;Seen&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`csv:"Seen"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Read file
&lt;/h3&gt;

&lt;p&gt;We are going to get to the crux of the project. We need to be able to read a CSV file called &lt;code&gt;example.csv&lt;/code&gt; located in the root directory. To achieve this, we're going to write a separate &lt;code&gt;ReadCsv()&lt;/code&gt; function to achieve this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ReadCsv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Try to open the example.csv file in read-write mode.&lt;/span&gt;
    &lt;span class="n"&gt;csvFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csvFileError&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"example.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDWR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModePerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// If an error occurs during os.OpenFIle, panic and halt execution.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;csvFileError&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvFileError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// Ensure the file is closed once the function returns&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;csvFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;
    &lt;span class="c"&gt;// Parse the CSV data into the articles slice. If an error occurs, panic.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unmarshalError&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gocsv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnmarshalFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;unmarshalError&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unmarshalError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The function &lt;code&gt;ReadCsv&lt;/code&gt; can be broken down into the following parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function returns an slice of pointers to the elements of type &lt;code&gt;Article&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;We use &lt;code&gt;os.OpenFile()&lt;/code&gt; to open &lt;code&gt;example.csv&lt;/code&gt; with specific flags in read-write mode. The flag &lt;code&gt;os.ModePerm&lt;/code&gt; indicates a the file mode for creating new files if necessary. This &lt;code&gt;openFile()&lt;/code&gt; either returns a file handle (&lt;code&gt;csvFile&lt;/code&gt;) or an error (&lt;code&gt;csvFileError&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Immediately in the next step, we check for errors. The &lt;code&gt;nil&lt;/code&gt; is Go's equivalent to null or empty. If an error was stored in the variable, we exit the function and report the error.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;defer csvFile.Close()&lt;/code&gt; makes sure that the opened &lt;code&gt;csvFile&lt;/code&gt; is always closed regardless of when the function return happens. This is best practice for file resource management. &lt;/li&gt;
&lt;li&gt;With the file open and error handling in place, we're going to proceed to parse the CSV content. The &lt;code&gt;gocsv.UnmarshalFile()&lt;/code&gt; function is provided with the file handle and the reference articles slice. It reads the CSV rows and populates the slice with &lt;code&gt;Article&lt;/code&gt; instances.&lt;/li&gt;
&lt;li&gt;If the parsing of the csvFile completes without errors, the &lt;code&gt;articles&lt;/code&gt; variable will be returned correctly.
### Filter articles
After successfully parsing the CSV file and storing its contents into an &lt;code&gt;articles&lt;/code&gt; slice, the next step is to filter this slice. We want to only retain the articles whose location is set to inbox. We're going to create a function called &lt;code&gt;GetInboxArticles&lt;/code&gt; to achieve this:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetInboxArticles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Initialize an empty slice to store inbox articles&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inboxArticles&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;

    &lt;span class="c"&gt;// Iterate through each article in the provided slice.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Check if the article's Location is equal to inbox&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"inbox"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// If the article's location is inbox, add it to the inboxArticles slice&lt;/span&gt;
            &lt;span class="n"&gt;inboxArticles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inboxArticles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inboxArticles&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's closely examine this function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This function accepts a slice of pointers to the &lt;code&gt;Article&lt;/code&gt; struct and returns a  slice of the same type.&lt;/li&gt;
&lt;li&gt;We create an empty slice called &lt;code&gt;inboxArticles&lt;/code&gt; that will store the articles that meet the inbox criteria. &lt;/li&gt;
&lt;li&gt;We create a for loop that's going to iterate through each element of the &lt;code&gt;articles&lt;/code&gt; slice. If the location property of the article is equal to &lt;code&gt;inbox&lt;/code&gt;, we append this element to the &lt;code&gt;inboxArticles&lt;/code&gt; slice. &lt;/li&gt;
&lt;li&gt;After the loop has finished, we return the slice &lt;code&gt;inboxArticles&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Write file
&lt;/h3&gt;

&lt;p&gt;Now that we've extracted the inbox articles, we want to persist this data into a new CSV file. Writing contents to a CSV file will be similar to reading the contents as in the previous steps. We're going to create a function &lt;code&gt;WriteCsv&lt;/code&gt; that looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WriteCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Open result.csv for writing; create it if it doesn't exist, or overwrite it if it already exists.&lt;/span&gt;
    &lt;span class="n"&gt;resultFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resultFileError&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_WRONLY&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_TRUNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModePerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Check for errors when opening or creating the file. If there's an error, panic.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resultFileError&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultFileError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resultFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Marshal the articles into the CSV format and write them to the result.csv file&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;marshalFileError&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gocsv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarshalFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resultFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;marshalFileError&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marshalFileError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's go through this piece of code step by step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We create a function &lt;code&gt;WriteCSV&lt;/code&gt; that accepts an input argument of &lt;code&gt;Article&lt;/code&gt; slice. &lt;/li&gt;
&lt;li&gt;We use  &lt;code&gt;os.OpenFile()&lt;/code&gt; to create or open the file &lt;code&gt;results.csv&lt;/code&gt;. The flags passed into the function ensure that the file is write-only, will be created if it doesn't exist, and overwritten if it already exists. &lt;/li&gt;
&lt;li&gt;After trying to open the file &lt;code&gt;result.csv&lt;/code&gt;, we check if the variable &lt;code&gt;resultFileError&lt;/code&gt; contains an error. If it does contain an error, we exit the function with the panic operator. &lt;/li&gt;
&lt;li&gt;For good I/O hygiene we ensure that the &lt;code&gt;resultFile&lt;/code&gt; is closed whenever the function exits with the &lt;code&gt;defer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, we are going to write the contents of the &lt;code&gt;articles&lt;/code&gt; to the &lt;code&gt;resultFile&lt;/code&gt; with &lt;code&gt;gocsv.MarshalFile()&lt;/code&gt;. The &lt;code&gt;MarshlFile&lt;/code&gt; function expects to arguments: reference to the slice, and the CSV file to which it should write the contents. If there was an error during the marshaling process, the function will panic. 
### Putting it all together
We've written three helper functions: Reading a CSV file, writing to a CSV file and filtering articles. We're going to combine all of these three into a main function like this:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ReadCsv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;filteredArticles&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetInboxArticles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;WriteCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filteredArticles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Run 🚀
&lt;/h2&gt;

&lt;p&gt;With all the Go code in place, it's time to run it! This can be done with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If done correctly, your project will have a new file named &lt;code&gt;result.csv&lt;/code&gt;. Congratulations, you have just run your first Go project! 🎉&lt;/p&gt;
&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;For our everyday task of processing a CSV file, we can see that Go's simplicity, efficiency and easy-to-learn syntax shine brightly. This makes it easy for new learners to pick up this powerful language and rich ecosystem of packages and utilities. Keeping an eye on new tools and languages like Go can expand your skills toolset and offer you a different vantage point to think and solve problems. Of course the best tool for the job will depend on your project's requirements. Perhaps you will consider integrating Go for your next software project. Happy coding! 🧑‍💻&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/duncanlew" rel="noopener noreferrer"&gt;
        duncanlew
      &lt;/a&gt; / &lt;a href="https://github.com/duncanlew/demo-go-csv-parser" rel="noopener noreferrer"&gt;
        demo-go-csv-parser
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;A Guide to Parsing CSV Files in Go&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;The tutorial for this project can be found on &lt;a href="https://duncanlew.medium.com/a-guide-to-parsing-csv-files-in-go-992b1636f139" rel="nofollow noopener noreferrer"&gt;Medium&lt;/a&gt; and &lt;a href="https://dev.to/duncanlew/a-guide-to-parsing-csv-files-in-go-4lp7" rel="nofollow"&gt;dev.to&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/duncanlew/demo-go-csv-parser" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
    </item>
    <item>
      <title>Migrate your data from Readwise Reader to Omnivore</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Wed, 10 Jan 2024 21:31:35 +0000</pubDate>
      <link>https://forem.com/duncanlew/migrate-your-data-from-readwise-reader-to-omnivore-4cpa</link>
      <guid>https://forem.com/duncanlew/migrate-your-data-from-readwise-reader-to-omnivore-4cpa</guid>
      <description>&lt;p&gt;Readwise Reader and Omnivore are both great read-later services. Each offers unique features and experiences. Although I was an avid user of Readwise Reader, I didn't use all of its features to their fullest. Omnivore, with its open-sourced nature, catered more closely to my needs. In situations where your needs change, migrating your saved content from Readwise Reader to Omnivore could become a necessity. Facing this issue myself, I decided to write my own tool to facilitate this process.&lt;/p&gt;

&lt;p&gt;I had two main reasons for developing this tool. Firstly, I wanted to address this need that I have to seamlessly transfer my content between these two services. Fortunately, Omnivore provides a very extensive API to achieve this. Secondly, this project served as an opportunity for me to learn Rust, a programming language popular for its performance and memory safety. &lt;/p&gt;

&lt;p&gt;This blog post will describe the steps you need to perform to import your data into Omnivore. The GitHub project with the source code can be found &lt;a href="https://github.com/duncanlew/Readwise-to-Omnivore-Importer" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;



&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To get started with the data migration tool, you will need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exported CSV file from Readwise Reader using the web interface. Check Readwise's &lt;a href="https://blog.readwise.io/p/f8c0f71c-fe5f-4025-af57-f9f65c53fed7/#howdoigenerateacsvofallmysaveddocuments" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; for the steps.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.omnivore.app/integrations/api.html#getting-an-api-token" rel="noopener noreferrer"&gt;API key&lt;/a&gt; from Omnivore&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Running the importer tool
&lt;/h2&gt;

&lt;p&gt;There are two ways to run the importer tool. The easiest way without requiring to install Rust is by using the binary. For those who would like to explore the repository or make changes to the source code, it's also possible to run the tool with the Rust build tools.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Running it with the binary
&lt;/h3&gt;

&lt;p&gt;A compiled binary is available for download in the project &lt;a href="https://github.com/duncanlew/readwise-to-omnivore-importer/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt; of this project.  &lt;/p&gt;
&lt;h4&gt;
  
  
  1. Download the binary
&lt;/h4&gt;

&lt;p&gt;Navigate to the &lt;a href="https://github.com/duncanlew/readwise-to-omnivore-importer/releases" rel="noopener noreferrer"&gt;Release&lt;/a&gt; page and download the latest version for your operating system  &lt;/p&gt;
&lt;h4&gt;
  
  
  2. Run the binary
&lt;/h4&gt;

&lt;p&gt;Open a terminal and navigate to the directory where you downloaded the binary. Add your CSV file in that directory and then run the following command with the two parameters replaced&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./readwise_to_omnivore_importer &lt;span class="nt"&gt;--key&lt;/span&gt; YOUR_API_KEY &lt;span class="nt"&gt;--file-path&lt;/span&gt;   PATH_TO_CSV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Running it locally
&lt;/h3&gt;

&lt;p&gt;To run the project locally, make sure to first install &lt;a href="https://www.rust-lang.org/tools/install" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; on your local machine. This should only take a few minutes. When that is done, open up a terminal and follow these steps:  &lt;/p&gt;
&lt;h4&gt;
  
  
  1. Clone the repository
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:duncanlew/readwise-to-omnivore-importer.git   
&lt;span class="nb"&gt;cd &lt;/span&gt;readwise-to-omnivore-importer    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  2. Add your CSV file
&lt;/h4&gt;

&lt;p&gt;Add your exported CSV file from Readwise Reader into the directory called &lt;code&gt;readwise-to-omnivore-importer&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Run the importer
&lt;/h4&gt;

&lt;p&gt;In the following command replace &lt;code&gt;YOUR_API_KEY&lt;/code&gt; and &lt;code&gt;PATH_TO_CSV&lt;/code&gt; with your api key and file path to your CSV, respectively.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo run &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--key&lt;/span&gt; YOUR_API_KEY &lt;span class="nt"&gt;--file-path&lt;/span&gt; PATH_TO_CSV 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Transitioning your reading list from Readwise Reader to Omnivore is a streamlined process thanks to their easy export and import tools and APIs. Moreover, creating your own tool using an unfamiliar tech can be both rewarding and educational. I hope this guide, along with the provided source code in &lt;a href="https://github.com/duncanlew/Readwise-to-Omnivore-Importer" rel="noopener noreferrer"&gt;the GitHub project&lt;/a&gt; inspires you to embark on your own journey of continuous learning. Happy coding! 😎&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/duncanlew" rel="noopener noreferrer"&gt;
        duncanlew
      &lt;/a&gt; / &lt;a href="https://github.com/duncanlew/Readwise-to-Omnivore-Importer" rel="noopener noreferrer"&gt;
        Readwise-to-Omnivore-Importer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Readwise to Omnivore importer&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;The "Readwise to Omnivore Importer" is a custom tool developed using Rust to facilitate the import of articles from
Readwise Reader into
Omnivore. It uses a CSV file exported from Readwise Reader using
the &lt;a href="https://blog.readwise.io/p/f8c0f71c-fe5f-4025-af57-f9f65c53fed7/#howdoigenerateacsvofallmysaveddocuments" rel="nofollow noopener noreferrer"&gt;web interface&lt;/a&gt;
The importer parses this CSV file, ensuring all data is transferred to Omnivore. Additionally, a verification check is
run to confirm the existence
of a URL before importing it into Omnivore. This is done to avoid polluting your Omnivore library
with broken links
Furthermore, the tool provides clear logging results and a summary of the import process.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Parse the exported CSV file&lt;/li&gt;
&lt;li&gt;URLs that are invalid won't be imported into Omnivore&lt;/li&gt;
&lt;li&gt;Import into Omnivore using an API key for authentication&lt;/li&gt;
&lt;li&gt;Detailed logging of invalid and errored results shown in the terminal and stored as a CSV file&lt;/li&gt;
&lt;li&gt;Possibility to run the project locally or use the tool as…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/duncanlew/Readwise-to-Omnivore-Importer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>rust</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Your First Chatbot using ChatGPT with TypeScript in NodeJS</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sun, 07 May 2023 17:42:09 +0000</pubDate>
      <link>https://forem.com/duncanlew/building-your-first-chatbot-using-chatgpt-with-typescript-in-nodejs-2gm3</link>
      <guid>https://forem.com/duncanlew/building-your-first-chatbot-using-chatgpt-with-typescript-in-nodejs-2gm3</guid>
      <description>&lt;p&gt;Artificial intelligence (AI) is rapidly becoming one of the most promising technologies, particularly with the introduction of generative AI chatbots. OpenAI launched its first AI chatbot in November 2022 and within a short couple of months, it has become &lt;a href="https://www.reuters.com/technology/chatgpt-sets-record-fastest-growing-user-base-analyst-note-2023-02-01/" rel="noopener noreferrer"&gt;the fastest-growing app in history&lt;/a&gt;. Who would have known that one of the most transformative technologies to be introduced in 2022 would be a simple text box to chat with a computer program that can give you an answer to almost any question that you ask it? Many industries have shifted their strategies to incorporate and adopt more AI technologies into their core services and products. &lt;/p&gt;

&lt;p&gt;It can be difficult to wrap your head around the rapid developments happening in the AI space, and many questions arise about how to best use AI to improve services. In this blog post, we'll explore the building blocks of how to build a simple NodeJS application to chat with ChatGPT using TypeScript. This is going to be a command-line interface to interact with the chatbot. &lt;/p&gt;

&lt;p&gt;What you will need to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; version 18 or newer&lt;/li&gt;
&lt;li&gt;IDE of choice (e.g. Visual Studio Code)&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;
  
  
  1. Retrieve your API key from OpenAI
&lt;/h2&gt;

&lt;p&gt;In order to get started, you will need to create an API key to use ChatGPT from your Node.js application. Navigate to &lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;https://platform.openai.com&lt;/a&gt;. If you’ve used ChatGPT before, you will have already created an OpenAI account and can log in immediately. If not, create your account first to get started. &lt;/p&gt;

&lt;p&gt;Once you’re signed in, click on the top right corner of your profile page to open up a dropdown menu. Select the option “View API keys”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fto1iqtyrvwrownspz7a8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fto1iqtyrvwrownspz7a8.png" alt="Menu for selecting viewing API keys" width="600" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A settings page will be shown in which you can generate a new secret key. Clicking on this button will trigger a modal to pop up displaying your new API key. Make sure to copy this value and store it in a safe place. This will be needed for the Node.js application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo84xzfwa8o2e2s620kp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo84xzfwa8o2e2s620kp.png" alt="Page for creating API keys" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Are there costs for using the API?
&lt;/h3&gt;

&lt;p&gt;Generating an API key, which is required to access the OpenAI API, can be done for free. As of the writing of this article, OpenAI provides users with a $5 credit that can be used to test their API without incurring any extra costs. If you wish to continue using the API after your free credits have been depleted, you will need to enter your billing information. It's important to keep in mind that OpenAI offers different pricing plans based on usage. Ensure your usage stays within budget if you decide to pay for a pricing plan.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Set up a project with the required dependencies
&lt;/h2&gt;

&lt;p&gt;For our Node.js application, we’re going to create a directory to get started. We’re going to call this directory &lt;code&gt;chatgpt-typescript&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;chatgpt-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we can navigate into the newly created directory to create the &lt;code&gt;src&lt;/code&gt; directory to store our source code and initialize an npm project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;chatgpt-typescript
&lt;span class="nb"&gt;mkdir &lt;/span&gt;src
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The flag &lt;code&gt;-y&lt;/code&gt; means that we’re going to accept the defaults for creating the npm project. &lt;/p&gt;

&lt;p&gt;Now we need to install the production and development dependencies of our project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;openai dotenv chalk
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node ts-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The dependency openai is package that provides wrapper methods to access the OpenAI API directly. Dotenv is a package that we use to easily load in environment variables. Finally, chalk is a package that we use to add beautifully colored styling to the terminal. &lt;/p&gt;
&lt;h2&gt;
  
  
  3. Set up the TypeScript compiler
&lt;/h2&gt;

&lt;p&gt;To get started with TypeScript in our project, we need to initialize a TypeScript configuration file. We can do this with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsc &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The TypeScript compiler will create a &lt;code&gt;tsconfig.json&lt;/code&gt; file with some default values. Replace the contents of that file with the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compilerOptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es2017&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ESNext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moduleResolution&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nodenext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esModuleInterop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;forceConsistentCasingInFileNames&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skipLibCheck&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rootDir&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outDir&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the file that we’ll be using for this blog post. We’ve defined that we will be using the modern ESM modules and that our root directory is inside &lt;code&gt;./src&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Inside our TypeScript files, we want to be able to use the &lt;code&gt;import&lt;/code&gt; syntax. To do that, we need to instruct the compiler to use JavaScript modules. We are also going to add a build script and a start script to the package.json file. This will be useful when we want to play with our command-line interface. Add the following lines to your &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tsc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run build &amp;amp;&amp;amp; node build/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Add your API key to the .env file
&lt;/h2&gt;

&lt;p&gt;The dependency &lt;code&gt;dotenv&lt;/code&gt; that we installed allows Node to load environment variables from the &lt;code&gt;.env&lt;/code&gt; file into its global variable called &lt;code&gt;process.env&lt;/code&gt;. The API key that we generated, needs to be loaded into our Node.js application. We don’t add this API key directly into our TypeScript file, but place this inside &lt;code&gt;.env&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file inside your root directory and add the following line to it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;replace-with-your-openai-api-key&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Make sure to replace the placeholder with the contents of your API key. &lt;/p&gt;
&lt;h2&gt;
  
  
  5. Write your first Chatbot in TypeScript
&lt;/h2&gt;

&lt;p&gt;Now it’s time to write our code to make an API call. Create an &lt;code&gt;index.ts&lt;/code&gt; in the &lt;code&gt;src&lt;/code&gt; directory. &lt;/p&gt;

&lt;p&gt;The first thing that we’re going to do is add the following import lines for the required packages:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OpenAIApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionRequestMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;chalk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chalk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The next step is to load in the OpenAI API key. We need the this key to make API calls to OpenAI to interact with ChatGPT.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load OpenAI API key from environment variable&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAIApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We need to initialize two objects. A messages array to store the history of our interaction with the chatbot. This helps the chatbot to keep the history of your messages in memory when giving you back a response. The second object that we need to initialize is the &lt;code&gt;userInterface&lt;/code&gt; object. We use this object to interact with the command-line interface.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initialize messages array&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionRequestMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize readline interface&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInterface&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now that we have initialized the &lt;code&gt;userInterface&lt;/code&gt;object, we can set the prompt that the user sees when their input is required with &lt;code&gt;.setPrompt()&lt;/code&gt;. After that, we add the &lt;code&gt;.prompt()&lt;/code&gt; method so that the interface is going to wait for the user to add their input.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set prompt for user&lt;/span&gt;
&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Send a message:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you see the message &lt;code&gt;Send a message:&lt;/code&gt; , the user can enter a prompt for the chatbot. We want the chatbot to receive this prompt when the user presses the Enter button. We can do this with the following lines of code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create request message and add it to messages array&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;requestMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionRequestMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Call OpenAI API to generate response&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChatCompletion&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Display response message to user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Prompt user for next message&lt;/span&gt;
  &lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When the user presses Enter in the command-line interface, the program triggers the &lt;code&gt;line&lt;/code&gt; event and extracts the user’s input from the &lt;code&gt;input&lt;/code&gt; variable. The program then creates a &lt;code&gt;ChatCompletionRequestMessage&lt;/code&gt; to store the input and stores it in the &lt;code&gt;messages&lt;/code&gt; array. This message’s &lt;code&gt;role&lt;/code&gt; property is set to &lt;code&gt;user&lt;/code&gt; to indicate that it represents a user’s input. When ChatGPT generates a response, the program creates another &lt;code&gt;ChatCompletionRequestMessage&lt;/code&gt; with the &lt;code&gt;role&lt;/code&gt; property set to &lt;code&gt;assistant&lt;/code&gt; to indicate that it presents the program’s response. &lt;/p&gt;

&lt;p&gt;Finally, the program is going to make an API call to OpenAI  generate a response based on the user’s input. This response is stored in the messages array and will be displayed in the command-line interface.&lt;/p&gt;

&lt;p&gt;Optionally, when the user quits the program, we can display an exit message:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Handle program exit&lt;/span&gt;
&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thank you for using this Demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The complete code with all the required interactions should look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OpenAIApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionRequestMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;chalk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chalk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load OpenAI API key from environment variable&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAIApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize messages array&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionRequestMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize readline interface&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInterface&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Set prompt for user&lt;/span&gt;
&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Send a message:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create request message and add it to messages array&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;requestMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionRequestMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Call OpenAI API to generate response&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChatCompletion&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Display response message to user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responseMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Prompt user for next message&lt;/span&gt;
  &lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Handle program exit&lt;/span&gt;
&lt;span class="nx"&gt;userInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thank you for using this Demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  6. Interact with your chatbot &lt;strong&gt;🤖&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now it’s time for the moment of truth! Run the following command in your terminal to start up your chatbot:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This command is going to transpile your TypeScript code to JavaScript and run the compiled code in &lt;code&gt;build/index.js&lt;/code&gt; . You can now start interacting with ChatGPT using your TypeScript code. Ask it anything that you want. An example of this interaction looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpcra7ju1o74glti6pc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpcra7ju1o74glti6pc9.png" alt="Interaction with ChatGPT inside the terminal" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;ChatGPT is a powerful tool that can be leveraged by developers to add chatbot functionality to their existing applications. With the new wave of technological innovations happening in AI, learning to use these tools can help you to understand these trends better and provide users with an enhanced experience. By following this tutorial, you can see that it’s quite straightforward to get started with a simple example of connecting to ChatGPT. The next step for you could be to think of ways to integrate a ChatGPT-powered chatbot into your current services or workflows. Happy coding! 😎&lt;/p&gt;

&lt;p&gt;The complete source code can be found &lt;a href="https://github.com/duncanlew/demo-chatgpt-typescript" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/duncanlew" rel="noopener noreferrer"&gt;
        duncanlew
      &lt;/a&gt; / &lt;a href="https://github.com/duncanlew/demo-chatgpt-typescript" rel="noopener noreferrer"&gt;
        demo-chatgpt-typescript
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Building Your First Chatbot using ChatGPT with TypeScript in NodeJS&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;The tutorial for this project can be found on &lt;a href="https://medium.com/@duncanlew/building-your-first-chatbot-using-chatgpt-with-typescript-in-nodejs-af9ebdf374c9" rel="nofollow noopener noreferrer"&gt;Medium&lt;/a&gt; and &lt;a href="https://dev.to/duncanlew/building-your-first-chatbot-using-chatgpt-with-typescript-in-nodejs-2gm3" rel="nofollow"&gt;dev.to&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Running locally&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;When running this locally, make sure to first create a &lt;code&gt;.env&lt;/code&gt; file and place your OpenAI API key in there. You can also check step 4 of my tutorial on &lt;a href="https://medium.com/@duncanlew/building-your-first-chatbot-using-chatgpt-with-typescript-in-nodejs-af9ebdf374c9" rel="nofollow noopener noreferrer"&gt;Medium&lt;/a&gt; or &lt;a href="https://dev.to/duncanlew/building-your-first-chatbot-using-chatgpt-with-typescript-in-nodejs-2gm3" rel="nofollow"&gt;dev.to&lt;/a&gt; for more background information on this.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/duncanlew/demo-chatgpt-typescript" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>node</category>
      <category>chatgpt</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Develop Anywhere, Anytime with GitHub Codespaces</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sat, 04 Feb 2023 19:44:10 +0000</pubDate>
      <link>https://forem.com/duncanlew/develop-anywhere-anytime-with-github-codespaces-125a</link>
      <guid>https://forem.com/duncanlew/develop-anywhere-anytime-with-github-codespaces-125a</guid>
      <description>&lt;h2&gt;
  
  
  A Guide to Setting Up and Using GitHub Codespaces for Streamlined Development
&lt;/h2&gt;

&lt;p&gt;Getting started with software development in any new project can be a daunting process. Depending on whether you’re running a Windows, Mac, or Linux operating system, the instructions to boot up the project correctly can differ a lot. There are many dependencies, extensions, and command-line tools to install and also network configurations that you might have to set up. And you haven’t even started writing your first line of code! 🤯 &lt;/p&gt;

&lt;p&gt;What if there is a way to skip the hassle of getting your local dev environment up and running and immediately start-up the project without any hassle? GitHub Codespaces is here to save the day! 🤩  GitHub Codespaces is a cloud-based development environment that comes fully configured with all the dependencies, IDE, and extensions that you need to start contributing your first line of code. The best part of GitHub Codespaces is that it can run in any browser opening up the possibility to use your iPad, Android tablet, or even your Chromebook as a development machine. You don’t have to worry anymore about the hardware specs of your device since the whole dev setup is running in the cloud. The only thing that you need is a stable internet connection. &lt;/p&gt;

&lt;p&gt;In this article, we’re going to explore how to get started with GitHub Codespaces and use a sample GitHub repository to start up the project, make a change and commit a line of code. Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you need to get started with GitHub Codespaces?
&lt;/h2&gt;

&lt;p&gt;There are many ways to use GitHub Codespaces. The method that we will be using in this article is to start Codespaces from inside your browser. There are also other ways to start it up directly with an IDE like Jetbrains IntelliJ or Visual Studio Code. These methods for starting up Codespaces from an IDE won’t be covered in this article. &lt;/p&gt;

&lt;p&gt;Anyone with a GitHub account can get started with Codespaces. It is free for individual use for up to 60 hours a month. After you exceed this limit, you will have to sign up for a plan to continue coding in the cloud-hosted environment. 60 hours a month should be ample time to decide whether you want to continue using this service full-time or as a side addition to your development tools. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start CodeSpaces from a GitHub repository
&lt;/h3&gt;

&lt;p&gt;You can start a Codespaces instance from any GitHub repository. I have set up a working React project in a &lt;a href="https://github.com/duncanlew/demo-codespaces" rel="noopener noreferrer"&gt;public repository&lt;/a&gt; that you can use for Codespaces. Feel free to test it out directly from my repository. &lt;/p&gt;

&lt;p&gt;When you open up the repository page, you will see a green &lt;strong&gt;Code&lt;/strong&gt; button in the upper right corner:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipgbjk9rew2eulbd04b3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipgbjk9rew2eulbd04b3.png" alt="GitHub page in which the green Code button is displayed" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on it to open up a menu. Select the &lt;strong&gt;Codespaces&lt;/strong&gt; tab and then a different green button called &lt;strong&gt;Create Codespace on main&lt;/strong&gt; will be shown. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4lgpkkw54su6x039lpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4lgpkkw54su6x039lpf.png" alt="Popup with the green button to create a Codespace" width="444" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on &lt;strong&gt;Create Codespace on main&lt;/strong&gt; will start up a cloud development environment. A loading screen will be shown while the creation is in progress:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco5n02dvcyecbxzkyidc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco5n02dvcyecbxzkyidc.png" alt="Progress screen for Codespace creation" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the cloud environment is up and running, you will see a Visual Studio Code instance running from inside your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowl812n2z7pp3tyqxy00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowl812n2z7pp3tyqxy00.png" alt="Visual Studio Code from inside the browser" width="800" height="776"&gt;&lt;/a&gt;&lt;br&gt;
You’re now ready to start using CodeSpaces! 💻&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Start the React project from inside Codespaces
&lt;/h3&gt;

&lt;p&gt;Now that your Codespaces instance is up and running. It’s time to do some actual coding! In the open terminal that is on display, type &lt;code&gt;npm run start&lt;/code&gt; and wait for the process to start up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywd1fak9868mfxrstr6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywd1fak9868mfxrstr6r.png" alt="Output for running npm run start" width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the React project has started up, it will open up a new tab in your browser in which you can see your front-end page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr7v1v9qu4naxwh0dsuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr7v1v9qu4naxwh0dsuy.png" alt="Standard React frontend page" width="800" height="773"&gt;&lt;/a&gt;&lt;br&gt;
You have just successfully started your React application from inside the cloud environment! No setup, dependency management or extensions on your own machine is required to make it work. 😎&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Make a code contribution inside Codespaces
&lt;/h3&gt;

&lt;p&gt;Let’s go back to our browser tab with Codespaces up and running. Navigate to the following file: src/App.tsx. On line 11 change the line to &lt;code&gt;GitHub Codespaces&lt;/code&gt; and save your changes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej7wzjna1bhbn2f0cpg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej7wzjna1bhbn2f0cpg9.png" alt="Source code for App.tsx" width="512" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to your tab in which you have your active front-end page running and refresh your page. You will see your changes immediately reflected on the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma7kb42gmodqkjfix0i0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma7kb42gmodqkjfix0i0.png" alt="React frontend page in which one line has been changed" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we’ve made our textual change in App.tsx, we can commit and push this change to our branch. This can also be easily done inside your Codespaces instance. You can either do this from the terminal or from the Source Control sidebar. Being able to perform git actions will depend on your access rights to the GitHub repository from which your Codespaces instance is running.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Note on port forwarding
&lt;/h3&gt;

&lt;p&gt;When you ran &lt;code&gt;npm run start&lt;/code&gt; in the previous steps, a local development server was started up from inside Codespaces. When an application inside Codespace starts a server with a &lt;code&gt;localhost&lt;/code&gt; URL, port forwarding will automatically get set up so that you can visit the served page from a URL that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghcl4lb4m2bwgea10sj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghcl4lb4m2bwgea10sj7.png" alt="Example frontend-page link for serving the React application" width="576" height="38"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This URL can only be accessed privately. This means that only you can access it with the GitHub account you used to start Codespaces. If you want to share this URL with friends or colleagues so that they can check out the web server from their own devices, you have to make sure that the PORT visibility is set to public. This can be done easily from within Codespaces itself. In the Panel window, click on the &lt;strong&gt;PORTS&lt;/strong&gt; tab. Our React application was being served on localhost:3000. The row with Port number 3000 is the one that corresponds to our local web application. Right-click on this row and click on Port Visibility and then Public to enable public access to your served React page. The options for changing the port visibility will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9hkv4l0f7d4v105sxbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9hkv4l0f7d4v105sxbw.png" alt="Option for changing the port visibility for the React frontend page" width="620" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Stop or terminate Codespaces
&lt;/h2&gt;

&lt;p&gt;When you’re done with your coding session in Codespaces, closing the browser tab is not enough to terminate Codespaces. The cloud instance will still be running on GitHub’s server and each hour being run counts up to your 60-hour monthly allowance. You can either choose to pause or stop your Codespaces or to delete your Codespace instance completely. This can be done on the page of the GitHub repository by clicking on the green &lt;strong&gt;Code&lt;/strong&gt; button and then clicking on the three dots of the active Codespace to show the termination options for your instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc5yyp4923c6a00z7l3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc5yyp4923c6a00z7l3o.png" alt="Options for stopping or deleting the Codespace instance" width="465" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;GitHub Codespaces is a powerful cloud-hosted development environment that provides developers with a secure and efficient way to code, build, and debug their projects. Forgo the hassle of owning a powerful development machine and installing all the required development dependencies. Being a productive developer on an iPad is made possible with Codespaces. With its easy setup and collaboration features, GitHub Codespaces makes it simple for developers to get started quickly and collaborate with others on their projects. Give it a try today and see how it can streamline your development work. Happy coding! 😎&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>woocommerce</category>
      <category>postmark</category>
      <category>email</category>
      <category>webhosting</category>
    </item>
    <item>
      <title>Back to the Basics with HTML Forms</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sat, 14 Jan 2023 19:15:41 +0000</pubDate>
      <link>https://forem.com/duncanlew/back-to-the-basics-with-html-forms-1l0j</link>
      <guid>https://forem.com/duncanlew/back-to-the-basics-with-html-forms-1l0j</guid>
      <description>&lt;p&gt;&lt;em&gt;Understand the intricacies of how the HTML Form works&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Forms are an essential part of any modern website that allows users to interact with data and submit information. Many modern frameworks like Angular and React have their own implementation and wrappers to facilitate the creation of HTML forms, but it's also important to understand what is happening behind the scenes when you're making an input form. In this article, we're going back to the basics of HTML forms and how to use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure of an HTML form
&lt;/h2&gt;

&lt;p&gt;An HTML form is composed of a set of elements for users to interact with. These elements can include an input field, a checkbox, and a submit button. All the information filled in from the form fields can be used to submit information to a web application.&lt;/p&gt;

&lt;p&gt;We’re going to create a simple form with two input fields for the username and email address and a submit button. In order to do this, we’re going to introduce three HTML elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;: The form element is used as a container for various user input fields&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;: This element allows input for our username and email address&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;: This element is a caption that is used for the input element&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using these three elements, we can create a simple HTML form like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Username&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Username"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"example@email.com"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this HTML sample, we see some attributes inside the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements. Let’s take a look at what they are. &lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element, the &lt;code&gt;for&lt;/code&gt; attribute is used to associate the label with a specific form control. In this case, the label "Username" is associated with the text field that has an &lt;strong&gt;&lt;code&gt;id&lt;/code&gt;&lt;/strong&gt; of "username".&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element. we’ve used a couple of attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; : This attribute is used to give the input form control a name. We can identify the correct input with this &lt;code&gt;name&lt;/code&gt; attribute when the form is submitted.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;placeholder&lt;/code&gt;: This is used to display placeholder text when the field hasn’t been filled in yet by the user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt;: The final &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element has the attribute &lt;code&gt;type&lt;/code&gt; with the value submit. This means that it will become a button that submits the form. All &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements can be given a &lt;code&gt;type&lt;/code&gt; attribute. If none is defined as in the case of username and email, the default one will be &lt;code&gt;text&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When this sample HTML form is displayed by the browser, it will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgnraayflzsxnwftpus3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgnraayflzsxnwftpus3.png" alt="Basic HTML form rendered in the browser" width="511" height="41"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Submitting the form
&lt;/h2&gt;

&lt;p&gt;We’ve created a complete working form with two input elements for username and email and a button to submit the data. What will actually happen when you click on the Submit button? The responsibilities are separated into two parts. The browser or the client is responsible for sending the form data, and the server is responsible for receiving and processing the form data. We’re going to take a look at what happens from the perspective of the browser or client. &lt;/p&gt;

&lt;p&gt;Let’s assume that we’ve filled in &lt;code&gt;helloworld&lt;/code&gt; and &lt;code&gt;my@email.com&lt;/code&gt; for the first two input fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj87j1h8v5ic6v31b3uhx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj87j1h8v5ic6v31b3uhx.png" alt="HTML form with filled in data" width="502" height="43"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click on the submit button, a couple of things are going to happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form data will be collected as a query string based on the input fields like this: &lt;code&gt;username=helloworld&amp;amp;email=my@email.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The current browser URL will be retrieved. If the form is being served from &lt;code&gt;localhost:8000&lt;/code&gt;, then that is what the current URL will be.&lt;/li&gt;
&lt;li&gt;The browser will navigate to the current address appended with the query string constructed from the form data. The formula for the constructed address is this: &lt;code&gt;&amp;lt;currentUrl&amp;gt;?&amp;lt;queryString&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The resulting address will be &lt;code&gt;localhost:8000?username=helloworld&amp;amp;email=my@email.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Navigating to this newly constructed URL is in its essence a GET request. It’s up to the server to be able to process this URL with the query string parameters. What we often see with form submissions, is that we would like to do a POST request instead. In addition to that, we might also want to post this request to a specific URL. This can all be achieved by adding the &lt;code&gt;method&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; attributes in the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://myformurl.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this form gets submitted, the browser is going to perform a &lt;code&gt;POST&lt;/code&gt; request to the URL &lt;a href="https://myformurl.com" rel="noopener noreferrer"&gt;https://myformurl.com&lt;/a&gt; with &lt;code&gt;username=helloworld&amp;amp;email=my@email.com&lt;/code&gt; as a payload. The request &lt;code&gt;content-type&lt;/code&gt; header will be set to &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;. It’s then up to the server to be able to process this form submission correctly with the given information. &lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieving the form as a JSON payload
&lt;/h2&gt;

&lt;p&gt;In many modern JavaScript frameworks like Angular, React and Vue, form submission is done via a JSON payload. These frameworks usually don’t use a GET request with query string parameters or a POST request with a payload of content-type &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;. In order to achieve this form extraction as a JSON payload, we are going to add an event listener to the HTML form and some client-side JavaScript. &lt;/p&gt;

&lt;p&gt;The event listener that we are going to add to the HTML form is called &lt;code&gt;onsubmit&lt;/code&gt;. This event is fired when a form is submitted. When this &lt;code&gt;onsubmit&lt;/code&gt; event is triggered, we want to call a function named &lt;code&gt;extractJson()&lt;/code&gt;. We can do it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"extractJson(event)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;event&lt;/code&gt; argument is passed to the &lt;code&gt;extractJson()&lt;/code&gt; function. This object is automatically generated by the browser when the form is submitted. &lt;/p&gt;

&lt;p&gt;We now need some JavaScript to extract the JSON from the HTML form. This can be achieved with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Send the JSON payload as a next step&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;event.preventdefault();&lt;/code&gt; is used to prevent the default behavior of the form submission to occur. The default behavior, in this case, is the GET request to the current URL with the form data as a query string parameter. &lt;/p&gt;

&lt;p&gt;After preventing the default form behavior, we’re going to use the FormData API to retrieve the FormData which is stored in &lt;code&gt;event.target&lt;/code&gt;. We use &lt;code&gt;Object.fromEntries&lt;/code&gt; to convert the &lt;code&gt;FormData&lt;/code&gt; to a plain JavaScript object and then log into the console. The expected console output will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"helloworld"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my@email.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resulting variable &lt;code&gt;jsonData&lt;/code&gt; can be used to send the form data to a server. &lt;/p&gt;

&lt;p&gt;I’ve created a working CodePen sandbox to illustrate the extraction of the form data with these HTML and JavaScript code samples. You can find it over here: &lt;a href="https://codepen.io/duncanlew/pen/mdjwMLO" rel="noopener noreferrer"&gt;https://codepen.io/duncanlew/pen/mdjwMLO&lt;/a&gt;. Feel free to play around with it! You can check out the console output by clicking on the Console button in the bottom left corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8gob277la95b7iv51zv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8gob277la95b7iv51zv.png" alt="Console button in CodePen" width="346" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve added some CSS in the code sandbox to style the form a bit, but this is of course outside the scope of this article. 💅 All the CSS styling can be commented out so you can see what the unstyled version of the form looks like. This has no effect on the functionality of the form submission. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9pxi720v0rn4ahvt0wz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9pxi720v0rn4ahvt0wz.png" alt="Styled" width="466" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;HTML forms are one of the basic building blocks in modern websites to interact with data and submit information. Understanding the basics of how HTML forms work and how to use them is an important part of becoming a proficient web developer. With the right configuration, you can even send the form data as a JSON object. Don’t be afraid to take a step back from the modern frameworks and investigate what is happening technically under the hood. Happy coding! 👨‍💻&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>discuss</category>
      <category>webdev</category>
      <category>community</category>
    </item>
    <item>
      <title>Streamline Your Java Development Workflow with SDKMAN</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sun, 08 Jan 2023 15:42:22 +0000</pubDate>
      <link>https://forem.com/duncanlew/streamline-your-java-development-workflow-with-sdkman-2092</link>
      <guid>https://forem.com/duncanlew/streamline-your-java-development-workflow-with-sdkman-2092</guid>
      <description>&lt;h1&gt;
  
  
  Switching between Java versions can be as easy as running a single command
&lt;/h1&gt;

&lt;p&gt;Do you have multiple versions of Java, Maven, Gradle, or other SDKs to manage? Are you tired of manually switching between different versions of these SDKs? SDKMAN to the rescue! SDKMAN is a command-line tool that allows you to quickly switch between various versions of SDKs, making it easier and more efficient to develop and test your applications. In this article, we'll discuss the benefits of using SDKMAN, provide some installation instructions, and explain how to use it to switch Java versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of using SDKMAN
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9w4hxifuamgfzfd0p8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9w4hxifuamgfzfd0p8y.png" alt="Logo of SDKMAN" width="800" height="466"&gt;&lt;/a&gt;&lt;br&gt;
SDKMAN is a great tool for developers because it makes it easier to manage and switch between different versions of Java. By default, you can only have one version of Java installed on your computer at a time. With SDKMAN, you can easily switch between different Java versions without needing to uninstall or reinstall Java every time.&lt;/p&gt;

&lt;p&gt;Another great benefit of SDKMAN is that it also works out of the box for other SDKs in the JVM ecosystem, like Maven, Gradle, Scala and SBT. You don’t have to install a different manager for each SDK. &lt;/p&gt;

&lt;p&gt;In addition to that, SDKMAN makes it easier to keep your Java versions up-to-date. SDKMAN will automatically check for updates and notify you when new versions of Java are available. This saves you time and effort, as you don't need to manually search for updates&lt;/p&gt;

&lt;p&gt;Finally, SDKMAN is a tool built in bash and is completely free to use. It's open-source and available for anyone to use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Installing SDKMAN is very easy. All you need to do is run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://get.sdkman.io"&lt;/span&gt; | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the command has been executed successfully, the output is going to look similar to this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd0qic35umtc9vg9khaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd0qic35umtc9vg9khaj.png" alt="Installation result of SDKMAN" width="800" height="1593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the command has been run, you can use SDKMAN to install and switch between different versions of Java. You might have to restart your Terminal before the &lt;code&gt;sdk&lt;/code&gt; commands will work. To check whether SDKMAN works in your terminal after the installation, type the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should be able to output the version of SDKMAN like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SDKMAN 5.16.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing Java versions
&lt;/h2&gt;

&lt;p&gt;SDKMAN makes it very easy to install different Java versions. To list all available Java versions that you can install, simply run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk list java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will list all the available versions of Java that you can install. If you want to install Java 11 from the vendor Temurin, you can look for the identifier in this list which is: 11.0.17-tem. Type &lt;code&gt;q&lt;/code&gt; to exit the list. Using the identifier for the Java version you want to install, run the following installation command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk &lt;span class="nb"&gt;install &lt;/span&gt;java 11.0.17-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After successfully installing Java 11, the output will look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdoygko05dacfc55kxq90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdoygko05dacfc55kxq90.png" alt="Installation result for Java 11" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you need to install Java 17 from Temurin, it’s as easy as copying the identifier from the output command of &lt;code&gt;sdk list java&lt;/code&gt; and placing it in the sdk install command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk &lt;span class="nb"&gt;install &lt;/span&gt;java 17.0.5-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Switching Java versions
&lt;/h2&gt;

&lt;p&gt;To switch between Java versions, you need to look for the identifier of your Java version in &lt;code&gt;sdk list java&lt;/code&gt; and copy that value to switch to. If you’d like to switch to Java 11 of Temurin, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk use java 11.0.17-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’d like to switch to Java 17 of Temurin, this would be the correct command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk use java 17.0.5-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making a specific Java version the default
&lt;/h2&gt;

&lt;p&gt;If you’d like to make a specific Java version the default, like version 17 of Temurin, you can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk default java 17.0.5-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How about Maven, Grade, and the rest?
&lt;/h2&gt;

&lt;p&gt;We’ve seen how to install and switch between Java versions. But how would you know which SDKs are supported? No worries! The complete list of SDKs supported by SDKMAN can be found here: &lt;a href="https://sdkman.io/sdks" rel="noopener noreferrer"&gt;https://sdkman.io/sdks&lt;/a&gt;. How do you install and switch between other JVM-based SDKs like Maven, Grade, and the rest? The principle is the same for Java. You simply have to replace the Java and identifier command with the one the SDK you want to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sdk list maven
sdk &lt;span class="nb"&gt;install &lt;/span&gt;maven &amp;lt;version-identifier&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;SDKMAN streamlines the management of all the SDKs that you’d need in the JVM ecosystem. We’ve covered how to easily install and switch Java versions and also how to do that for other SDKs that you’d need. SDKMAN is definitely a tool worth checking out and can help you save time and effort in the process. This is especially helpful for teams that are working on multiple projects with different SDK version requirements. Happy coding! 😎&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Six Essential Tips for Boosting your JavaScript skills</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sat, 31 Dec 2022 16:10:21 +0000</pubDate>
      <link>https://forem.com/duncanlew/six-essential-tips-for-boosting-your-javascript-skills-27gi</link>
      <guid>https://forem.com/duncanlew/six-essential-tips-for-boosting-your-javascript-skills-27gi</guid>
      <description>&lt;p&gt;JavaScript was introduced in 1995 and its standards are actively being maintained  by ECMA International - hence the name ECMAScript for JavaScript. Since 2015, major versions of the JavaScript language are being introduced every year in June. So JavaScript is constantly evolving, and new features are regularly being added to the language. Are you looking to take your JavaScript skills to the next level with all the relevant new features? Whether you're a beginner or an experienced developer, there are always ways to improve and learn new techniques. In this blog post, we'll explore six essential tips that can help you level up your JavaScript skills. For each tip, we'll also point out which ECMAScript version is required to be able to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Optional Chaining
&lt;/h2&gt;

&lt;p&gt;Have you tried accessing nested properties inside an object in which the intermediate properties might be undefined? To avoid your code from throwing errors like &lt;code&gt;Cannot read properties of undefined&lt;/code&gt;, we write very convoluted statements wrapped with &amp;amp;&amp;amp; operators like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript has a feature called optional chaining which is a way to access or modify properties of objects without worrying if an intermediate property is undefined. The statement will simply return undefined without throwing an error and ending your code execution prematurely. This optional chaining allows you to write cleaner, more concise code. By using optional chaining, you can avoid having to write multiple checks to verify the existence of an object before attempting to access it. &lt;/p&gt;

&lt;p&gt;Rewriting the same if statement with optional chaining will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feature was introduced in ES2020.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Nullish coalescing operator
&lt;/h2&gt;

&lt;p&gt;When you want to assign a value to a variable, you might be placing null and undefined checks to make sure that you’re retrieving the intended value. If that’s not the case, we might want to provide a fallback value in that case. Such a code sample could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Anonymous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JavaScript, there is a nullish coalescing operator (??) that allows developers to provide a fallback value when working with null or undefined values. This operator can be used to check for null or undefined values and provide an alternative value when the initial value is null or undefined. &lt;/p&gt;

&lt;p&gt;For example, you can use the nullish coalescing operator to simplify the code sample from above like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Anonymous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feature was introduced in ES2020.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Maps Instead of For Loop
&lt;/h2&gt;

&lt;p&gt;When you start a JavaScript programming course, one of the first topics typically taught is for loops. For loops are a type of loop that repeats a set of instructions a certain number of times. They are used to iterate over data structures like arrays or objects and can be used to perform a wide variety of tasks. &lt;/p&gt;

&lt;p&gt;If you want to iterate through an array and store the value of each number squared in a new array, it would look like this with a for loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the introduction of newer, more modern features in JavaScript, for loops are not always the best option for some tasks. Maps are a powerful data structure that can often be used instead of for loops to improve performance. The map() method takes a callback function as its argument and returns an array with the results of calling the callback on each element in the array. This allows you to quickly and easily iterate over an array without having to write a loop. &lt;/p&gt;

&lt;p&gt;The benefit of using a Map is that it doesn’t cause side effects in your code and always allows you to create a new array. The resulting code with a Map would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feature was introduced in ES2015 or ES6.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Arrow Functions
&lt;/h2&gt;

&lt;p&gt;Arrow functions are a concise way to define functions in JavaScript, and they have several benefits over traditional function declarations. Arrow functions have a shorter syntax than regular functions, which can make your code more readable. Additionally, arrow functions are lexically scoped, meaning that the value of the this keyword inside an arrow function is the same as the value outside of it. This can be very helpful when working with objects and methods.&lt;/p&gt;

&lt;p&gt;For example, consider the following regular function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be written as an arrow function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This feature was introduced in ES2015 or ES6.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Object Destructuring
&lt;/h2&gt;

&lt;p&gt;Object destructuring is a feature of JavaScript that allows you to quickly and easily extract values from an object. It allows you to create variables that contain the values of specific properties in an object, which can be useful when you need to access the same properties repeatedly.&lt;/p&gt;

&lt;p&gt;For example, consider the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPhone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using object destructuring, you can extract the values from the user object and assign them to variables like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPhone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feature was introduced in ES2015 or ES6.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Object literal enhancement
&lt;/h2&gt;

&lt;p&gt;Now that you know what Object destructuring is, it’s time to take a look at object literal enhancement. This is a feature of JavaScript that is the exact opposite of object destructuring. It allows you to grab variables and create objects with fewer lines of code. For example, consider the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPhone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using object literal enhancement, you can simplify this code to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPhone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This feature was introduced in ES2015 or ES6.&lt;/p&gt;

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

&lt;p&gt;JavaScript is constantly getting enriched by new features every year. By following these up-to-date tips and continuing to learn and grow, you can level up your JavaScript skills and become a more proficient and successful developer. Don't be afraid to challenge yourself and try new things – it's the best way to improve and keep up with the ever-changing world of technology. Happing coding! 😎&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Easily Format and Highlight Your Code in Google Docs</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sun, 18 Dec 2022 17:29:58 +0000</pubDate>
      <link>https://forem.com/duncanlew/easily-format-and-highlight-your-code-in-google-docs-433g</link>
      <guid>https://forem.com/duncanlew/easily-format-and-highlight-your-code-in-google-docs-433g</guid>
      <description>&lt;p&gt;Google Docs is a popular word processing tool that allows you to create and edit documents online. Code formatting in Google Docs hasn’t always been an easy task to achieve. If you wanted to put a code block directly into your Google Docs document, you would have ended up with unformatted code with no syntax highlighting. This isn’t always easy on the eyes and is not conducive to quickly scanning your code to get a sense of its purpose. There are other ways to achieve this in a more aesthetically pleasing way, but that meant having to use add-ons as a work-around. But fret no more, Google has got you covered! One of the new notable features that Google just &lt;a href="https://workspaceupdates.googleblog.com/2022/12/format-display-code-google-docs.html" rel="noopener noreferrer"&gt;announced&lt;/a&gt; is Code Formatting, which makes your code easier to read and understand. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkmb0c3x55kw7mdu4yqd2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkmb0c3x55kw7mdu4yqd2.gif" alt="Gif illustrating how to use code formatting. Source: Google" width="1061" height="798"&gt;&lt;/a&gt;&lt;br&gt;
To access this code-formatting feature in your current Google Docs document, select Insert &amp;gt; Building blocks &amp;gt; Code Blocks. Afterwards, select the programming language of your choice. It’s also possible to access this feature by typing &lt;code&gt;@project assets&lt;/code&gt; in the document and selecting Code blocks.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Availability
&lt;/h2&gt;

&lt;p&gt;According to Google’s post, the code formatting feature in Google Docs will be available to all Google Workspace customers starting on January 3, 2023. Google has not stated yet that they have plans to roll it out to those with personal Google Accounts in the future. Hopefully this will change in the near future. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Google Docs is constantly getting improved with new features and this time it’s a boon for us developers out there! Code formatting in Google Docs is a useful tool that can help you create documents that are easier to read and understand. Whether you are working on a document that includes a lot of code or just want to insert a few lines of code into your document, Google Docs has you covered. 👨‍💻&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>A better Node version manager: Volta vs nvm</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Tue, 29 Nov 2022 20:11:19 +0000</pubDate>
      <link>https://forem.com/duncanlew/a-better-node-version-manager-volta-vs-nvm-3f2h</link>
      <guid>https://forem.com/duncanlew/a-better-node-version-manager-volta-vs-nvm-3f2h</guid>
      <description>&lt;h2&gt;
  
  
  A better Node version manager: Volta vs nvm
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="[https://nodejs.org/en/](https://nodejs.org/en/)"&gt;Node.js&lt;/a&gt; ecosystem has been undergoing developments to its JavaScript tools, which makes the JavaScript tooling that we use more robust, reliable, and fast. One specific tool that we’re going to take a look at is the version manager for Node.js. When you’re frequently switching between different Node.js repositories, managing the specific versions of Node should be a very mundane process. The established management tool recommended by many for Node is nvm. Just because nvm has been around for a long time and is used by many developers does not necessarily mean that it’s the best tool for the job. There are other toolchains like Volta that claim to do a better job than nvm. Today, we’re going to take a look at whether these claims hold water and how to immediately get started with Volta on your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The new kid in town: Volta
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzcoksdvy6szjfxjdmc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzcoksdvy6szjfxjdmc4.png" alt="Volta logo" width="400" height="221"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="[https://volta.sh/](https://volta.sh/)"&gt;Volta&lt;/a&gt; is a JavaScript tool built on Rust for managing different Node.js versions. This tool can run on Windows, macOS, and Linux systems, making it a truly universal version manager for Node.js with cross-platform support. Volta claims to be very fast and seamless in its startup time and per-project version switching. Is Volta faster and snappier than the established nvm for managing your Node.js versions? We’re going to put these claims to the test. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting started with Volta&lt;/strong&gt;&lt;br&gt;
Volta is very easy to install on your system. If you have a macOS or a Linux machine, you can install Volta with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://get.volta.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The successful output for installing Volta will look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin73xo2rkivwfn4550f5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin73xo2rkivwfn4550f5.png" alt="Installation result of Volta" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This script will install Volta and update your bash, zsh, or fish startup script immediately. &lt;/p&gt;

&lt;p&gt;If you have a Windows machine, you can download and run the installer from the following &lt;a href="https://github.com/volta-cli/volta/releases/download/v1.1.0/volta-1.1.0-windows-x86_64.msi" rel="noopener noreferrer"&gt;link&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;After the installation you can install Node.js version 18 like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;volta &lt;span class="nb"&gt;install &lt;/span&gt;node@18
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sounds counterintuitive, but switching to a different Node.js version with Volta is also done by using the &lt;code&gt;install&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;volta &lt;span class="nb"&gt;install &lt;/span&gt;node@16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last &lt;code&gt;volta install&lt;/code&gt; command that is run, makes that Node.js version the default one.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Benchmarking approach 📏
&lt;/h3&gt;

&lt;p&gt;In order to test Volta’s speed claims, we’re going to use two MacBooks with the following CPU configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;16-inch 2019, 2.6 GHz 6-Core Intel Core i7&lt;/li&gt;
&lt;li&gt;16-inch 2021, Apple M1 Pro&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For brevity in the rest of the article, we’re going to call these machines an Intel machine and an M1 machine. On both the Intel and M1 machine, we’re using zsh as the default shell and have nvm installed. For benchmarking, we’re going to use the zsh profiler and measure the time needed for a zsh startup to interactivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zsh profiler&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll profile the startup times of our Terminal with nvm in the startup script and see how this differs when we install Volta. This way we can see if Volta delivers on its speed promises compared to nvm. &lt;/p&gt;

&lt;p&gt;Profiling the startup time in zsh can be done with the built-in profiler called zprof. This can be done very easily by placing &lt;code&gt;zmodload zsh/zprof&lt;/code&gt; at the beginning of your &lt;code&gt;~/.zshrc&lt;/code&gt; file and &lt;code&gt;zprof&lt;/code&gt; at the bottom. We can use the profiling results to analyze which processes are being run and how time-consuming they are during the zsh startup. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup time measurement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Measuring the startup time before zsh becomes interactive can be done by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time  &lt;/span&gt;zsh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;strong&gt;Putting Volta’s speed claims to the test with benchmarks 🏎️&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now it’s time to get down to the nitty-gritty of actually running the measurements! When we run the zsh profiler for the Intel machine, we get the following results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;num  calls                &lt;span class="nb"&gt;time                       &lt;/span&gt;self            name
&lt;span class="nt"&gt;-----------------------------------------------------------------------------------&lt;/span&gt;
 1&lt;span class="o"&gt;)&lt;/span&gt;    1         446.15   446.15   90.42%    169.93   169.93   34.44%  nvm_auto
 2&lt;span class="o"&gt;)&lt;/span&gt;    2         276.22   138.11   55.98%    151.08    75.54   30.62%  nvm
 3&lt;span class="o"&gt;)&lt;/span&gt;    1         102.95   102.95   20.86%     86.29    86.29   17.49%  nvm_ensure_version_installed
 4&lt;span class="o"&gt;)&lt;/span&gt;    1          21.97    21.97    4.45%     16.77    16.77    3.40%  nvm_die_on_prefix
 5&lt;span class="o"&gt;)&lt;/span&gt;    1          16.66    16.66    3.38%     16.66    16.66    3.38%  nvm_is_version_installed
 6&lt;span class="o"&gt;)&lt;/span&gt;    2          15.11     7.56    3.06%     15.11     7.56    3.06%  compaudit
 7&lt;span class="o"&gt;)&lt;/span&gt;    1          22.02    22.02    4.46%      6.91     6.91    1.40%  compinit
 8&lt;span class="o"&gt;)&lt;/span&gt;    1           6.32     6.32    1.28%      6.32     6.32    1.28%  &lt;span class="o"&gt;(&lt;/span&gt;anon&lt;span class="o"&gt;)&lt;/span&gt;
 9&lt;span class="o"&gt;)&lt;/span&gt;    1           4.88     4.88    0.99%      4.88     4.88    0.99%  nvm_grep
10&lt;span class="o"&gt;)&lt;/span&gt;    1           4.68     4.68    0.95%      4.68     4.68    0.95%  zrecompile
11&lt;span class="o"&gt;)&lt;/span&gt;    1           4.44     4.44    0.90%      4.44     4.44    0.90%  __sdkman_export_candidate_home
12&lt;span class="o"&gt;)&lt;/span&gt;    1           3.97     3.97    0.80%      3.97     3.97    0.80%  __sdkman_prepend_candidate_to_path
13&lt;span class="o"&gt;)&lt;/span&gt;    1           1.32     1.32    0.27%      1.32     1.32    0.27%  regexp-replace
14&lt;span class="o"&gt;)&lt;/span&gt;   14           1.10     0.08    0.22%      1.10     0.08    0.22%  compdef
15&lt;span class="o"&gt;)&lt;/span&gt;    5           0.92     0.18    0.19%      0.92     0.18    0.19%  is-at-least
16&lt;span class="o"&gt;)&lt;/span&gt;    1           0.84     0.84    0.17%      0.84     0.84    0.17%  colors
17&lt;span class="o"&gt;)&lt;/span&gt;    4           0.54     0.14    0.11%      0.54     0.14    0.11%  add-zsh-hook
18&lt;span class="o"&gt;)&lt;/span&gt;    2           0.48     0.24    0.10%      0.48     0.24    0.10%  bashcompinit
19&lt;span class="o"&gt;)&lt;/span&gt;    4           5.21     1.30    1.05%      0.33     0.08    0.07%  nvm_npmrc_bad_news_bears
20&lt;span class="o"&gt;)&lt;/span&gt;    1           0.21     0.21    0.04%      0.21     0.21    0.04%  nvm_has
21&lt;span class="o"&gt;)&lt;/span&gt;    6           0.15     0.03    0.03%      0.15     0.03    0.03%  is_plugin
22&lt;span class="o"&gt;)&lt;/span&gt;    4           0.11     0.03    0.02%      0.11     0.03    0.02%  pathadd
23&lt;span class="o"&gt;)&lt;/span&gt;    1           0.08     0.08    0.02%      0.08     0.08    0.02%  _install-omp-hooks
24&lt;span class="o"&gt;)&lt;/span&gt;    1           0.15     0.15    0.03%      0.07     0.07    0.02%  &lt;span class="nb"&gt;complete
&lt;/span&gt;25&lt;span class="o"&gt;)&lt;/span&gt;    3           0.07     0.02    0.01%      0.07     0.02    0.01%  is_theme
26&lt;span class="o"&gt;)&lt;/span&gt;    2           0.04     0.02    0.01%      0.04     0.02    0.01%  env_default
27&lt;span class="o"&gt;)&lt;/span&gt;    1           0.03     0.03    0.01%      0.03     0.03    0.01%  fig_reset_hooks
28&lt;span class="o"&gt;)&lt;/span&gt;    1         446.18   446.18   90.42%      0.03     0.03    0.01%  nvm_process_parameters
29&lt;span class="o"&gt;)&lt;/span&gt;    1           0.03     0.03    0.01%      0.03     0.03    0.01%  detect-clipboard
30&lt;span class="o"&gt;)&lt;/span&gt;    2           0.02     0.01    0.00%      0.02     0.01    0.00%  __sdkman_echo_debug
31&lt;span class="o"&gt;)&lt;/span&gt;    1           0.01     0.01    0.00%      0.01     0.01    0.00%  nvm_is_zsh

&lt;span class="nt"&gt;-----------------------------------------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this profiling table, we can see that more than 90% of the startup time is occupied by nvm. This is a very shocking revelation. &lt;/p&gt;

&lt;p&gt;Running the same profiler for the M1 machine produces a similar result. We can also see that in the case of the M1 Mac more than 90% of the startup time is consumed by nvm processes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;num  calls                &lt;span class="nb"&gt;time                       &lt;/span&gt;self            name
&lt;span class="nt"&gt;-----------------------------------------------------------------------------------&lt;/span&gt;
 1&lt;span class="o"&gt;)&lt;/span&gt;    2         211.82   105.91   74.12%    115.62    57.81   40.46%  nvm
 2&lt;span class="o"&gt;)&lt;/span&gt;    1          79.78    79.78   27.92%     68.59    68.59   24.00%  nvm_ensure_version_installed
 3&lt;span class="o"&gt;)&lt;/span&gt;    1         256.20   256.20   89.65%     44.38    44.38   15.53%  nvm_auto
 4&lt;span class="o"&gt;)&lt;/span&gt;    2          14.99     7.49    5.24%     14.99     7.49    5.24%  compaudit
 5&lt;span class="o"&gt;)&lt;/span&gt;    1          14.95    14.95    5.23%     14.86    14.86    5.20%  nvm_die_on_prefix
 6&lt;span class="o"&gt;)&lt;/span&gt;    1          11.20    11.20    3.92%     11.20    11.20    3.92%  nvm_is_version_installed
 7&lt;span class="o"&gt;)&lt;/span&gt;    1           4.30     4.30    1.51%      4.30     4.30    1.51%  zrecompile
 8&lt;span class="o"&gt;)&lt;/span&gt;    1           4.15     4.15    1.45%      4.15     4.15    1.45%  &lt;span class="o"&gt;(&lt;/span&gt;anon&lt;span class="o"&gt;)&lt;/span&gt;
 9&lt;span class="o"&gt;)&lt;/span&gt;    1          18.29    18.29    6.40%      3.30     3.30    1.15%  compinit
10&lt;span class="o"&gt;)&lt;/span&gt;    1           1.47     1.47    0.51%      1.47     1.47    0.51%  nvm_has
11&lt;span class="o"&gt;)&lt;/span&gt;    1           0.77     0.77    0.27%      0.77     0.77    0.27%  regexp-replace
12&lt;span class="o"&gt;)&lt;/span&gt;    1           0.49     0.49    0.17%      0.49     0.49    0.17%  colors
13&lt;span class="o"&gt;)&lt;/span&gt;    5           0.48     0.10    0.17%      0.48     0.10    0.17%  is-at-least
14&lt;span class="o"&gt;)&lt;/span&gt;   13           0.46     0.04    0.16%      0.46     0.04    0.16%  compdef
15&lt;span class="o"&gt;)&lt;/span&gt;    4           0.29     0.07    0.10%      0.29     0.07    0.10%  add-zsh-hook
16&lt;span class="o"&gt;)&lt;/span&gt;    6           0.09     0.02    0.03%      0.09     0.02    0.03%  is_plugin
17&lt;span class="o"&gt;)&lt;/span&gt;    4           0.09     0.02    0.03%      0.09     0.02    0.03%  nvm_npmrc_bad_news_bears
18&lt;span class="o"&gt;)&lt;/span&gt;    4           0.07     0.02    0.02%      0.07     0.02    0.02%  pathadd
19&lt;span class="o"&gt;)&lt;/span&gt;    1           0.08     0.08    0.03%      0.04     0.04    0.01%  &lt;span class="nb"&gt;complete
&lt;/span&gt;20&lt;span class="o"&gt;)&lt;/span&gt;    1           0.04     0.04    0.01%      0.04     0.04    0.01%  _install-omp-hooks
21&lt;span class="o"&gt;)&lt;/span&gt;    3           0.03     0.01    0.01%      0.03     0.01    0.01%  is_theme
22&lt;span class="o"&gt;)&lt;/span&gt;    1           0.03     0.03    0.01%      0.03     0.03    0.01%  fig_reset_hooks
23&lt;span class="o"&gt;)&lt;/span&gt;    2           0.02     0.01    0.01%      0.02     0.01    0.01%  bashcompinit
24&lt;span class="o"&gt;)&lt;/span&gt;    1         256.21   256.21   89.65%      0.01     0.01    0.00%  nvm_process_parameters
25&lt;span class="o"&gt;)&lt;/span&gt;    2           0.01     0.01    0.00%      0.01     0.01    0.00%  env_default
26&lt;span class="o"&gt;)&lt;/span&gt;    1           0.01     0.01    0.00%      0.01     0.01    0.00%  detect-clipboard
27&lt;span class="o"&gt;)&lt;/span&gt;    1           0.00     0.00    0.00%      0.00     0.00    0.00%  nvm_is_zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Measuring startup time to interactivity between nvm and Volta
&lt;/h4&gt;

&lt;p&gt;Now that we’ve profiled the startup of zsh until it becomes interactive, it’s time to measure the actual startup time of zsh itself. This measurement can be done by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;zsh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will output 3 parameters: the user, system, and total startup time. This is an example of the output command in which I’ve replaced the actual numbers with ***.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;***&lt;/span&gt; user &lt;span class="k"&gt;***&lt;/span&gt; system 75% cpu &lt;span class="k"&gt;***&lt;/span&gt; total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All these numbers are expressed in seconds. The parameter that we are going to focus on for the startup time is the last one which is the total startup time. &lt;/p&gt;

&lt;p&gt;On our Intel machine, we are going to measure the zsh startup time twice. Once with nvm turned on and the other time with Volta enabled. This is the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Intel zsh startup time&lt;/span&gt;
0.72s user 0.60s system 75% cpu 1.744 total &lt;span class="c"&gt;# with nvm&lt;/span&gt;
0.15s user 0.16s system 97% cpu 0.314 total &lt;span class="c"&gt;# with Volta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are some shocking numbers! The total startup time with nvm is 1.744 seconds. When we enabled Volta as our Node version manager, the startup time dropped to 0.314 seconds. Volta is faster by 1.43 seconds. This is a whopping reduction of 82%! Imagine all the seconds you save waiting for your Terminal to startup zsh. 💨&lt;/p&gt;

&lt;p&gt;The next step is to also measure the startup time for our M1 machine. M1 is no slouch to begin with. We wonder whether using Volta would even be worth it on an M1 machine that already has one of the industry’s best-optimized silicon chips based on the ARM architecture. These are the M1 results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# M1 zsh startup time&lt;/span&gt;
0.14s user 0.21s system 70% cpu 0.496 total &lt;span class="c"&gt;# with nvm&lt;/span&gt;
0.06s user 0.07s system 74% cpu 0.174 total &lt;span class="c"&gt;# with Volta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;M1’s startup time with nvm is only 0.496 seconds. This is already more than a second faster than the Intel machine with nvm, which has a startup time of 1.744 seconds. However, if we enable Volta on M1 the startup time can be reduced even more to 0.174 seconds. We can shave off 0.322 seconds of startup time with Volta. This computes to a startup time reduced by 65%! M1 had no trouble starting up quickly with nvm, but it can become even faster with Volta! ⚡️&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Takeaway
&lt;/h3&gt;

&lt;p&gt;The JavaScript ecosystem is developing at an even faster pace than before. Keeping up with all these new technological developments in the JavaScript scene can be daunting. New tooling that gets developed like Volta certainly dares to challenge the established norm like nvm. We were able to achieve a reduction in the startup time of 82% and 60% for an Intel and Mac machine, respectively. The biggest contributing factor to Volta being so snappy seems to be its foundation which is built on Rust. We might see even more tooling in the future being built with Rust or Go which makes good use of parallelism and memory optimizations. I hope that this article can shed some light on one of the new tools in the JavaScript environment. Happing coding! 😎&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Elevate Your Terminal Experience on The Mac 🧘‍♂️</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sat, 05 Nov 2022 20:26:51 +0000</pubDate>
      <link>https://forem.com/duncanlew/elevate-your-terminal-experience-on-the-mac-3o3m</link>
      <guid>https://forem.com/duncanlew/elevate-your-terminal-experience-on-the-mac-3o3m</guid>
      <description>&lt;h2&gt;
  
  
  The key ingredients to spice up your terminal are Oh My Zsh, Oh My Posh, and Fig
&lt;/h2&gt;

&lt;p&gt;The macOS operating system comes preinstalled with a default Terminal app. As a software engineer, data scientist or if you like geeking around, there's a big chance that you will be spending a lot of time in the terminal running a variety of commands. The terminal with its traditional white text on a black screen is quite bland and uninspiring. It doesn't invite you to stay in that window in order to get more stuff done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknp6ija6ti9iow43h79v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknp6ija6ti9iow43h79v.png" alt="Unmodified Terminal on Mac with default settings" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upgrading your terminal experience can be done for productivity reasons. You can install a bunch of shortcuts, plugins, and helpers to speed up your everyday processes. Making your terminal more powerful can of course also be done for aesthetic reasons. It will definitely become a more delightful experience every time you use the terminal.&lt;/p&gt;

&lt;p&gt;I will outline a five-step process to make your Terminal experience more powerful. Using your terminal will never be the same again after you've seen what's possible. Let's get started!&lt;/p&gt;



&lt;h3&gt;
  
  
  1. Replace the Mac Terminal 🕺
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcvnktxsjnzmsnu0rskx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcvnktxsjnzmsnu0rskx.png" alt="Screenshot of the Warp terminal" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default Terminal app for Mac leaves a lot to be desired. Other terminal emulators of competitors provide many extra features that are indispensable for the everyday Terminal user like split pane view, mouseless copy, and a paste history just to name a few.&lt;/p&gt;

&lt;p&gt;To get started, I would recommend using &lt;a href="https://iterm2.com/" rel="noopener noreferrer"&gt;iTerm&lt;/a&gt; and seeing whether this Terminal emulator fits your needs. Visually, it is the most similar to the built-in Terminal app with added features on top. If you're more adventurous you can try out &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt; or &lt;a href="https://hyper.is/" rel="noopener noreferrer"&gt;Hyper&lt;/a&gt;. They offer more cutting-edge features like AI-assisted command search and more editor-like command input.&lt;/p&gt;

&lt;p&gt;Before installing iTerm, we need to install &lt;a href="https://formulae.brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; first. Homebrew is a package manager for Mac and allows you to install packages by just executing them in your terminal application. If you haven't installed Homebrew yet, please install this first by running the following in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iTerm can now be installed with brew by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; iterm2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install a Zsh configuration and plugins 📦
&lt;/h3&gt;

&lt;p&gt;Zsh is the default shell that comes pre-installed on all macOS systems since macOS Catalina. Zsh works as an interpreter that translates your terminal commands into instructions for your operating system to run. Zsh on itself isn't very powerful. To improve this, we will install &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;Oh My Zsh&lt;/a&gt; which is a framework for managing your Zsh configuration. This will allow you to install plugins for your Zsh configuration.&lt;/p&gt;

&lt;p&gt;To install Oh My Zsh, simply open up your terminal emulator of choice and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you've installed Oh My Zsh, the output is going to look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vic4ju0q6d6b91u719f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vic4ju0q6d6b91u719f.png" alt="The output of the Terminal after installing Oh My Zsh." width="800" height="743"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh My Zsh comes pre-installed with the git plugin. This gives you a bunch of git aliases that you can use from your terminal. A handy few are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gst &lt;span class="o"&gt;=&lt;/span&gt; git status
gaa &lt;span class="o"&gt;=&lt;/span&gt; git add &lt;span class="nt"&gt;--all&lt;/span&gt;
gl  &lt;span class="o"&gt;=&lt;/span&gt; git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the complete list, please check out the &lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git" rel="noopener noreferrer"&gt;git plugin page&lt;/a&gt; for Oh My Zsh.&lt;/p&gt;

&lt;p&gt;To install other plugins for your Zsh configuration, open the following file in your editor of choice: &lt;code&gt;~/.zshrc&lt;/code&gt;. The currently installed plugins are denoted by the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;git&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exhaustive list of all possible plugins can be found on this Oh My Zsh &lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins" rel="noopener noreferrer"&gt;plugins page&lt;/a&gt;. Do you want to have aliases for 1Password, Spotify, Visual Studio Code, or one for the many engineering scripts that you run? Someone probably created a plugin for that. I would recommend installing the &lt;a href="https://github.com/agkozak/zsh-z" rel="noopener noreferrer"&gt;z plugin&lt;/a&gt;, which allows you to quickly navigate to a directory you have visited before with minimal keystrokes. Adding extra plugins to your zsh configuration can be done by simply editing the plugin line of the &lt;code&gt;.zshrc&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;git z brew&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving your changes, make sure to restart your terminal emulator or run the following command to make use of your latest changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Install a theming engine 💅
&lt;/h3&gt;

&lt;p&gt;This is probably the most exciting step: theming your terminal emulator! The theming engine that we're going to use is &lt;a href="https://ohmyposh.dev/" rel="noopener noreferrer"&gt;Oh My Posh&lt;/a&gt;. Oh My Posh has a lot of preconfigured themes and colors for your terminal prompt and makes it very easy to install and configure.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1 Oh My Posh
&lt;/h4&gt;

&lt;p&gt;You can install the Oh My Posh theming engine with Homebrew by executing the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;jandedobbeleer/oh-my-posh/oh-my-posh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.2 Fonts
&lt;/h4&gt;

&lt;p&gt;Oh My Posh recommends installing Nerd Fonts. These fonts are patched to include beautiful icons which will be displayed in your terminal prompt. Fonts can be installed with the built-in Oh My Posh command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oh-my-posh font &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running this command, you will be prompted to select a specific font. We will choose Meslo, since Oh My Posh recommends this font for its theming engine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftjr1kzqipgz6590u4v5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftjr1kzqipgz6590u4v5i.png" alt="Font selection during installation" width="800" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After having installed the Meslo font, you will probably need to configure this font in your terminal application of choice. Setting this up in iTerm settings will look approximately like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnr8alljbaq3b8i4kbwv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnr8alljbaq3b8i4kbwv9.png" alt="Selecting the right font in the iTerm preferences." width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If all is set properly, your terminal should now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht5r0gnotuovvfazkfe6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht5r0gnotuovvfazkfe6.png" alt="Oh My Posh installed and font configured correctly in iTerm" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3 Optional: choose a different theme
&lt;/h4&gt;

&lt;p&gt;You don't have to use the theme that Oh My Posh comes preinstalled with. That's the beauty of it. You can choose any of the predefined themes that are available on &lt;a href="https://ohmyposh.dev/docs/themes" rel="noopener noreferrer"&gt;this page&lt;/a&gt;. To configure the theme look for the following line in your &lt;code&gt;.zshrc&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;oh-my-posh init zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you've settled on a specific theme like M365Princess, you can change the Oh My Posh command in &lt;code&gt;.zshrc&lt;/code&gt; file into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eval "$(oh-my-posh init zsh --config $(brew --prefix oh-my-posh)/themes/M365Princess.omp.json)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to rerun the command &lt;code&gt;source ~/.zshrc&lt;/code&gt; to activate your changes. 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Beautify your ls command 👩‍🎤
&lt;/h3&gt;

&lt;p&gt;When you use your terminal, you list the content of your directory very frequently. The list that is outputted to your screen is very unoriginal and not easy to scan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5p09y48usa9eizrv57y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5p09y48usa9eizrv57y.png" alt="ls command with standard output" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wouldn't it be nice if the output of your ls command could be more distinguishable and easier to scan? &lt;a href="https://the.exa.website/" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; to the rescue! Exa is a replacement for the boring ls command and improves it with color, icons, and additional metadata. To install exa, run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;exa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exa doesn't actually replace the ls command in your terminal. To run exa, you will have to give it a bunch of flags to denote what configuration you want your ls command to contain. We can make aliases to replace the standard ls command with exa so that you don't have to type all the flags every time you want to list the content of a directory. We will add 3 aliases for the commonly used ls commands: ls, ll, and la. Open up your .zshrc file and add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'exa --icons --group-directories-first'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;ll&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'exa -l --icons --no-user --group-directories-first  --time-style long-iso'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;la&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'exa -la --icons --no-user --group-directories-first  --time-style long-iso'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rerun &lt;code&gt;source ~/.zshrc&lt;/code&gt; and use the exa command anywhere and be delighted with the output! 🤩&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpp0epod76tzsp5owq8hp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpp0epod76tzsp5owq8hp.png" alt="exa output in a repository" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Finishing touches: autocomplete magic 🦄
&lt;/h3&gt;

&lt;p&gt;What is a terminal without some autocomplete magic? The intellisense for code completion you get with modern IDEs has become indispensable in a software developer's job. Why can't this intellisense be extended to the terminal? Well, that's possible with &lt;a href="https://fig.io/" rel="noopener noreferrer"&gt;Fig&lt;/a&gt;! Fig is an extension that adds some autocomplete greatness to the most commonly used terminal emulators. It works smoothly to autocomplete many commands you can think of, like cdand git . To install Fig run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;fig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing Fig, open the application from your Application folder and follow the startup process to configure it correctly. When all it's done, you should be left with a terminal emulator that is a feast for your eyes. 🤩 Try using the git add command in a project to see how Fig suggests your unstaged files in the suggestion box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjrpp3w7cd47qah98gec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjrpp3w7cd47qah98gec.png" alt="Terminal emulator with autocomplete features" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;Getting your terminal application to look sharp shouldn't be a difficult and daunting task. Almost all of this can be done quickly by just executing a bunch of commands in your terminal to get the desired results. It's a very small time investment that pays dividends. It may not make you more productive per se, but it will certainly make you feel like a boss using it! Happing coding! 😎&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Explain and Translate Your Code With GitHub Copilot</title>
      <dc:creator>Duncan Lew</dc:creator>
      <pubDate>Sun, 21 Aug 2022 18:49:00 +0000</pubDate>
      <link>https://forem.com/duncanlew/explain-and-translate-your-code-with-github-copilot-3lin</link>
      <guid>https://forem.com/duncanlew/explain-and-translate-your-code-with-github-copilot-3lin</guid>
      <description>&lt;h2&gt;
  
  
  Boost your understanding with natural language descriptions of your code
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot is the new kid in town with a unique set of features that even seasoned developers can use to their advantage. One of the main selling points of GitHub Copilot is its ability to provide suggested lines of code assisted with AI. A specific feature that doesn’t get much attention is called Copilot Explain.&lt;/p&gt;

&lt;p&gt;Copilot Explain, as the name suggests, will translate code into natural language descriptions. This can be an immense help to developers who are unfamiliar with a certain project or programming language. This feature can also translate code from one programming language to another. This is a boon for instances when you find a code snippet solution in a different programming language and need to translate it to the target language of the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements to get started
&lt;/h3&gt;

&lt;p&gt;GitHub Copilot is free to use for students and open-source contributors. In all other cases, you can try it out with a trial to see if it fits your needs. In order to get started with GitHub Copilot, you will need an IDE. For this example, we will be using &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As of writing, the feature Copilot Explain is in technical preview and requires you to sign up for the tester group. Check out &lt;a href="https://githubnext.com/projects/copilot-labs/" rel="noopener noreferrer"&gt;GitHub Next&lt;/a&gt; to see how you can be part of the technical preview.&lt;/p&gt;



&lt;h3&gt;
  
  
  1. Install the extensions for GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;Inside your Visual Studio Code IDE, install the following two extensions for Visual Studio Code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-labs" rel="noopener noreferrer"&gt;GitHub Copilot Labs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxx2e200rcugn7xm16q9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxx2e200rcugn7xm16q9.png" alt="Prompt to configure GitHub Copilot with your GitHub account" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A popup will prompt you to configure Copilot. Follow the setup wizard to connect your GitHub account to GitHub Copilot.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Highlight the code to be explained
&lt;/h3&gt;

&lt;p&gt;For this example, we are going to simulate a situation in which you’re working with an unfamiliar repository that uses the Kotlin programming language. Kotlin is a very modern language introduced in 2011 and it might not necessarily be a language that all developers are familiar with.&lt;/p&gt;

&lt;p&gt;The code sample that we are going to use is from the &lt;a href="https://play.kotlinlang.org/byExample/04_functional/01_Higher-Order%20Functions" rel="noopener noreferrer"&gt;Kotlin example page&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;If you aren’t familiar with the Kotlin syntax, some parts might not necessarily make sense immediately and you might want to have a quick way to understand what this piece of code does without taking a deep dive into the intricate workings of Kotlin itself.&lt;/p&gt;

&lt;p&gt;Make sure to have the example code sample available in a file inside Visual Studio Code as example.kt. Highlight all the lines in this file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Explain the code
&lt;/h3&gt;

&lt;p&gt;If the Github Copilot extensions are installed correctly, you should have an extra GitHub icon available in your vertical activity bar:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6ylzhnbi0pncc77kvvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6ylzhnbi0pncc77kvvl.png" alt="Vertical activity bar with the GitHub Copilot Labs icon" width="500" height="868"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking on the GitHub icon, your sidebar should like look this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbb5vzmovo4z4gr6vy166.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbb5vzmovo4z4gr6vy166.png" alt="Explain section of the side bar" width="800" height="699"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven’t highlighted your code yet, make sure to do it now and then click on the button named &lt;strong&gt;Ask Copilot&lt;/strong&gt;. It will show the result as a translation of the code into plain English:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fozqg1t5le2xcjpb9k6ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fozqg1t5le2xcjpb9k6ui.png" alt="Result of explaining the code into plain English" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that’s how you can retrieve a description of what unfamiliar code snippet does in plain English! 🤓&lt;/p&gt;

&lt;h3&gt;
  
  
  4.  Translate the code into a different programming language
&lt;/h3&gt;

&lt;p&gt;If you found this code sample in Kotlin, and would like to translate it into a language that you are familiar with, GitHub Copilot can help you out! Let’s try to translate the code from Kotlin into Python. The GitHub Copilot activity bar has a second section called &lt;strong&gt;Language Translation&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvmchbj51hg7zj5agxuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvmchbj51hg7zj5agxuy.png" alt="Language translation section in the side bar" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the blue button named &lt;strong&gt;Ask Copilot&lt;/strong&gt; to translate your code. The end result will look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ry7e44556iuytfl7hgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ry7e44556iuytfl7hgq.png" alt="Result of translating the code from Kotlin to Python&amp;lt;br&amp;gt;
" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There you go! You just translated a code snippet from Kotlin to Python! 🔎&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;GitHub Copilot can be a revolutionary code completion tool by suggesting lines of code to the developer based on a comprehensive machine learning model. It certainly isn’t a tool to replace the developer. It’s a tool to elevate the development experience as if there is someone next to you helping you out from time to time with good suggestions. This can especially be useful for boilerplate and repetitive code.&lt;/p&gt;

&lt;p&gt;The new features that we introduced in this article for explaining and translating code are still in technical preview and gives us a glimpse into what the next step could be in AI-assisted programming. Make sure to keep an eye on tools like GitHub Copilot to see where the industry is heading next! 🚀&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
If the content was helpful, feel free to support me &lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/duncanlew" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fdefault-orange.png" alt="Buy Me A Coffee" width="434" height="100"&gt;&lt;/a&gt;

</description>
    </item>
  </channel>
</rss>
