<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tim Veletta</title>
    <description>The latest articles on DEV Community by Tim Veletta (@timmahh).</description>
    <link>https://dev.to/timmahh</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%2F14840%2F7a621096-c2f6-4c4b-bd87-3c9e22d44753.jpeg</url>
      <title>DEV Community: Tim Veletta</title>
      <link>https://dev.to/timmahh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/timmahh"/>
    <language>en</language>
    <item>
      <title>Array vs Set vs Object vs Map</title>
      <dc:creator>Tim Veletta</dc:creator>
      <pubDate>Fri, 18 Aug 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/timmahh/array-vs-set-vs-object-vs-map-586d</link>
      <guid>https://dev.to/timmahh/array-vs-set-vs-object-vs-map-586d</guid>
      <description>&lt;p&gt;JavaScript provides a variety of data structures for storing data. However, developers often tend to use &lt;code&gt;Array&lt;/code&gt;s and &lt;code&gt;Object&lt;/code&gt;s to solve most problems without considering whether they are the appropriate data structure. The use of &lt;code&gt;Set&lt;/code&gt;s instead of &lt;code&gt;Array&lt;/code&gt;s and &lt;code&gt;Map&lt;/code&gt;s instead of &lt;code&gt;Object&lt;/code&gt;s can provide numerous benefits. This document provides a guide on how to use &lt;code&gt;Set&lt;/code&gt; and &lt;code&gt;Map&lt;/code&gt;, as well as the reasons why they should be considered over &lt;code&gt;Array&lt;/code&gt;s and &lt;code&gt;Object&lt;/code&gt;s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Array and Set
&lt;/h2&gt;

&lt;p&gt;Consider a list of unique user IDs that gets appended to by some external operation. It would be more efficient to manage this list using a &lt;code&gt;Set&lt;/code&gt; rather than an &lt;code&gt;Array&lt;/code&gt; since a &lt;code&gt;Set&lt;/code&gt; &lt;strong&gt;doesn't allow duplicate items&lt;/strong&gt;. Below is an example of how &lt;code&gt;Set&lt;/code&gt; works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userIds&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;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&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;userIdSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [1, 2, 3, 4, 4, 7]&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Set(5) { 1, 2, 3, 4, 7 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Items
&lt;/h3&gt;

&lt;p&gt;Adding a new item to an &lt;code&gt;Array&lt;/code&gt; can be done using &lt;code&gt;push&lt;/code&gt; to add items to the end of the &lt;code&gt;Array&lt;/code&gt; or &lt;code&gt;unshift&lt;/code&gt; to add them to the start of the &lt;code&gt;Array&lt;/code&gt;. In terms of performance, using &lt;code&gt;push&lt;/code&gt; appends an element to the end of the &lt;code&gt;Array&lt;/code&gt; with &lt;strong&gt;constant time complexity&lt;/strong&gt; or *&lt;strong&gt;*O(1)**&lt;/strong&gt;. If &lt;code&gt;unshift&lt;/code&gt; is used, an element with an index of 0 is added and all the other elements are shifted by 1, resulting in &lt;strong&gt;linear time complexity&lt;/strong&gt; or &lt;strong&gt;O(n)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In contrast, &lt;code&gt;Set&lt;/code&gt; only allows the &lt;code&gt;add&lt;/code&gt; function to add an element to the end of the &lt;code&gt;Set&lt;/code&gt;, providing it with &lt;strong&gt;constant time complexity&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [17, 1, 2, 3, 4, 4, 7, 12]&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Set { 1, 2, 3, 4, 7, 12, 17 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Accessing Items
&lt;/h3&gt;

&lt;p&gt;Accessing values in &lt;code&gt;Array&lt;/code&gt;s and &lt;code&gt;Set&lt;/code&gt;s differs greatly due to how they are stored in memory. &lt;code&gt;Array&lt;/code&gt;s are &lt;strong&gt;indexed collections&lt;/strong&gt; , which means that they are stored sequentially by index. In contrast, &lt;code&gt;Set&lt;/code&gt;s are &lt;strong&gt;keyed collections&lt;/strong&gt; that use a hash table internally to store data using keys.&lt;/p&gt;

&lt;p&gt;Therefore, &lt;code&gt;Array&lt;/code&gt; elements can be accessed by index with &lt;strong&gt;O(1)&lt;/strong&gt; time complexity. However, if we want to find an ID within the &lt;code&gt;Array&lt;/code&gt;, we would have to iterate over the &lt;code&gt;Array&lt;/code&gt; using &lt;code&gt;find&lt;/code&gt; or &lt;code&gt;findIndex&lt;/code&gt;, resulting in a time complexity of &lt;strong&gt;O(n)&lt;/strong&gt;. If we want to know if the &lt;code&gt;Array&lt;/code&gt; contains a certain ID value, we can use the &lt;code&gt;includes&lt;/code&gt; function, which also iterates through all the items of the Array.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;Set&lt;/code&gt;s, we only have access to the &lt;code&gt;has&lt;/code&gt; function, which produces the same result as the &lt;code&gt;includes&lt;/code&gt; function in &lt;code&gt;Array&lt;/code&gt;s. However, because it is a keyed collection, the &lt;code&gt;has&lt;/code&gt; function has a &lt;strong&gt;constant time complexity&lt;/strong&gt; or &lt;strong&gt;O(1)&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// 7&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&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;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Removing Items
&lt;/h3&gt;

&lt;p&gt;Removing an item from an &lt;code&gt;Array&lt;/code&gt; can be done using &lt;code&gt;filter&lt;/code&gt; to filter out the matching items or &lt;code&gt;splice&lt;/code&gt; to remove the matching item by index. The former method only iterates through the items once with a time complexity of &lt;strong&gt;O(n)&lt;/strong&gt;, while the latter method iterates through the items twice with a time complexity of &lt;strong&gt;O(n&lt;sup&gt;2&lt;/sup&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a &lt;code&gt;Set&lt;/code&gt;, we can simply use the &lt;code&gt;delete&lt;/code&gt; function to remove the matching item, providing it with a time complexity of &lt;strong&gt;O(1)&lt;/strong&gt; due to it being a keyed collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;removeUserByIdSplice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;userId&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;userId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;removeUserByIdFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;userIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;userId&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;userId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;removeUserByIdSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;
  
  
  When Should You Use an Array Over a Set?
&lt;/h3&gt;

&lt;p&gt;Although there are numerous reasons to use a &lt;code&gt;Set&lt;/code&gt; over an &lt;code&gt;Array&lt;/code&gt;, sometimes having duplicate items in a list is desirable. Additionally, &lt;code&gt;Array&lt;/code&gt;s have many useful functions, such as &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, and &lt;code&gt;reduce&lt;/code&gt;, which allow us to perform operations on our collections.&lt;/p&gt;

&lt;p&gt;Because a &lt;code&gt;Set&lt;/code&gt; is iterable, it allows you to use the &lt;code&gt;for..of&lt;/code&gt; construct or spread operator (&lt;code&gt;…&lt;/code&gt;) to perform &lt;code&gt;Array&lt;/code&gt;-like functions. Thus, you can easily convert a &lt;code&gt;Set&lt;/code&gt; to an &lt;code&gt;Array&lt;/code&gt; to make use of the additional functions an &lt;code&gt;Array&lt;/code&gt; provides.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from set to array&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;userIdSet&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// [1, 2, 3, 4, 7, 12, 17]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Object and Map
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;Array&lt;/code&gt;s and &lt;code&gt;Set&lt;/code&gt;s are used to manage lists of data, &lt;code&gt;Object&lt;/code&gt;s and &lt;code&gt;Map&lt;/code&gt;s are used to manage key-value pairs. &lt;code&gt;Object&lt;/code&gt;s are another example of a data structure that developers often overuse when they should consider using a &lt;code&gt;Map&lt;/code&gt; instead. One of the main cases where you would want to consider using a &lt;code&gt;Map&lt;/code&gt; over an &lt;code&gt;Object&lt;/code&gt; is when keys are being added or deleted frequently.&lt;/p&gt;

&lt;p&gt;You can easily instantiate a new &lt;code&gt;Map&lt;/code&gt; based on an &lt;code&gt;Object&lt;/code&gt; using the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Perth&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;usersMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { tim: { id: 12, city: 'Perth' } }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Map(1) { 'tim' =&amp;gt; { id: 12, city: 'Perth' } }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Key-Value Pairs
&lt;/h3&gt;

&lt;p&gt;Adding to an &lt;code&gt;Object&lt;/code&gt; or a &lt;code&gt;Map&lt;/code&gt; has &lt;strong&gt;constant time complexity&lt;/strong&gt;. However, there is a bit of a gotcha with the &lt;code&gt;Map&lt;/code&gt; code below. If we run this in JavaScript, it wouldn't complain. However, we might notice that &lt;code&gt;henry&lt;/code&gt; doesn't get added in the way we expect. Instead, it gets added as an object property to the &lt;code&gt;Map&lt;/code&gt;, and hence it cannot be accessed using the &lt;code&gt;Map&lt;/code&gt; &lt;code&gt;get&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Melbourne&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Melbourne&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// 🚩&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { tim: { id: 12, city: 'Perth' }, henry: { id: 17, city: 'Melbourne' } }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Map { 'tim' =&amp;gt; { id: 12, city: 'Perth' }, henry: { id: 17, city: 'Melbourne' } }&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;henry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { id: 17, city: 'Melbourne' }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, we must use the &lt;code&gt;set&lt;/code&gt; function to add a key-value pair to a &lt;code&gt;Map&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Melbourne&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Map { 'tim' =&amp;gt; { id: 12, city: 'Perth' }, 'henry' =&amp;gt; { id: 17, city: 'Melbourne' } }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we try to do something similar to an &lt;code&gt;Object&lt;/code&gt; in TypeScript, we encounter issues with the &lt;code&gt;usersObject&lt;/code&gt; because it infers the type when it is instantiated. To work around this, we need to define the type as &lt;code&gt;{ [key: string]: { id: number, city: string } }&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TypeScript&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Perth&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;usersMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// 🚩 Property 'henry' does not exist on type '{ tim: { id: number; city: string; }; }'.&lt;/span&gt;
&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Melbourne&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Melbourne&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;h3&gt;
  
  
  Accessing Key-Value Pairs
&lt;/h3&gt;

&lt;p&gt;Again, accessing key-value pairs has a &lt;strong&gt;constant time complexity&lt;/strong&gt; for both &lt;code&gt;Object&lt;/code&gt;s and &lt;code&gt;Map&lt;/code&gt;s. The &lt;code&gt;get&lt;/code&gt; function is used to access a value from within a &lt;code&gt;Map&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;henry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { id: 17, city: 'Melbourne' }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// { id: 17, city: 'Melbourne' }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Removing Key-Value Pairs
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;delete&lt;/code&gt; operator is one of my least understood operators in JavaScript, so I tend to avoid it. I came across this brilliant article on &lt;a href="http://perfectionkills.com/understanding-delete/#delete_and_host_objects"&gt;Understanding delete&lt;/a&gt; that I highly recommend. Instead, when removing a property from an &lt;code&gt;Object&lt;/code&gt;, I prefer to use the spread operator, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;henry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// OR const { henry, ...newUsersObject } = usersObject;&lt;/span&gt;
&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;henry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { tim: { id: 12, city: 'Perth' } }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Map { 'tim' =&amp;gt; { id: 12, city: 'Perth' } }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thankfully, &lt;code&gt;Map&lt;/code&gt;s have it easy using the &lt;code&gt;delete&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;If we are using TypeScript, we would encounter the following error when trying to use the &lt;code&gt;delete&lt;/code&gt; function on an &lt;code&gt;Object&lt;/code&gt; because it is inferring that &lt;code&gt;tim&lt;/code&gt; is a required property of the &lt;code&gt;usersObject&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TypeScript&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Perth&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="c1"&gt;// 🚩 The operand of a 'delete' operator must be optional.&lt;/span&gt;
&lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;usersObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tim&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;h3&gt;
  
  
  Other reasons to consider using a Map
&lt;/h3&gt;

&lt;p&gt;One of the interesting properties of a &lt;code&gt;Map&lt;/code&gt; is that you're not restricted to using string values as keys, unlike with &lt;code&gt;Object&lt;/code&gt;s. Therefore, you could have some additional metadata included in your keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userLocationMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;tim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Veletta&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;henry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Henry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jones&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Perth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;henry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Melbourne&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Map { { id: 12, firstName: 'Tim', lastName: 'Veletta' } =&amp;gt; 'Perth', { id: 17, firstName: 'Henry', lastName: 'Jones' } =&amp;gt; 'Melbourne' }&lt;/span&gt;

&lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tim&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'Perth'&lt;/span&gt;
&lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;henry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'Melbourne'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another property of a &lt;code&gt;Map&lt;/code&gt; that could be useful is that the order of elements is preserved based on the order of insertion. This is also true for &lt;code&gt;Object&lt;/code&gt;s in later versions of the JavaScript standard. However, it is not guaranteed, so developers cannot rely on it. Like &lt;code&gt;Set&lt;/code&gt;s, &lt;code&gt;Map&lt;/code&gt;s are iterable, meaning they can use the &lt;code&gt;for..of&lt;/code&gt; construct to iterate over the elements in the &lt;code&gt;Map&lt;/code&gt;. Alternatively, the &lt;code&gt;forEach&lt;/code&gt; function can be used to do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;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;userLocation&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userLocation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [{ id: 12, firstName: 'Tim', lastName: 'Veletta' }, 'Perth'] [{ id: 17, firstName: 'Henry', lastName: 'Jones' }, 'Melbourne']&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;userLocationMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;location&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="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; lives in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;location&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="c1"&gt;// Tim lives in Perth Henry lives in Melbourne&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these operations has a time complexity of &lt;strong&gt;O(n)&lt;/strong&gt; because we are only iterating over the items once. However, trying to do the same thing with &lt;code&gt;Object&lt;/code&gt;s requires converting the &lt;code&gt;Object&lt;/code&gt; to a list using either &lt;code&gt;Object.keys&lt;/code&gt;, &lt;code&gt;Object.values&lt;/code&gt;, or &lt;code&gt;Object.entries&lt;/code&gt;, which, in itself, is an &lt;strong&gt;O(n)&lt;/strong&gt; operation. If you then need to do something with each of the values, you end up iterating over the items again, resulting in a time complexity of &lt;strong&gt;O(n&lt;sup&gt;2&lt;/sup&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Map&lt;/code&gt; also has a handy &lt;code&gt;size&lt;/code&gt; property, so you can always find out how many items are present. With an &lt;code&gt;Object&lt;/code&gt;, you have to convert it to a list and get the &lt;code&gt;length&lt;/code&gt; property from there.&lt;/p&gt;

&lt;p&gt;Congratulations on making it this far! I hope you have learned more about Sets and Maps and will consider using them more in the future. Below are a few resources that were used in creating this post if you want to learn more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#objects_vs._maps"&gt;MDN - Objects vs Maps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@ashfaqueahsan61/time-complexities-of-common-array-operations-in-javascript-c11a6a65a168"&gt;Time Complexities Of Common Array Operations In JavaScript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tharinducs.medium.com/time-complexity-of-objects-and-arrays-js-algo-02-5cd8ead23b91"&gt;Time Complexity of Objects and Arrays&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>datastructures</category>
    </item>
    <item>
      <title>The easiest way to send emails with Resend</title>
      <dc:creator>Tim Veletta</dc:creator>
      <pubDate>Thu, 06 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/timmahh/the-easiest-way-to-send-emails-with-resend-3127</link>
      <guid>https://dev.to/timmahh/the-easiest-way-to-send-emails-with-resend-3127</guid>
      <description>&lt;p&gt;If you're looking for an easy way to send emails from your Node.js or Next.js applications, &lt;a href="https://resend.com"&gt;Resend&lt;/a&gt; might be the answer. Resend is focused on the developer experience, making it easy for you to send emails. They are also the creators of &lt;a href="https://react.email/"&gt;React Email&lt;/a&gt;, an open-source library that simplifies creating formatted emails for sending.&lt;/p&gt;

&lt;p&gt;To get started with Resend, sign up and verify the domain you want to send emails from. Domain verification is simple with Resend, thanks to their focus on reducing friction which you can &lt;a href="https://resend.com/blog/new-domain-verification-experience"&gt;read about on their blog&lt;/a&gt;. You can then add Resend to your project with:&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;resend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding Resend, initialize it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resend&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;Resend&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;RESEND_API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RESEND_API_KEY&lt;/code&gt; can be created on the "API Keys" page in Resend. You can now send emails with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contact@timveletta.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// must verify the from domain&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timothy.veletta@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resend is easy!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;p&amp;gt;You should try it&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using Vercel Functions, add your API Key to the Environment Variables on Vercel, deploy your app, and hit your API endpoint. You can also check the "Emails" page in Resend to see what emails it has sent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RXWLRKHp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0hc2fifz4r5v4syhu7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RXWLRKHp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0hc2fifz4r5v4syhu7r.png" alt="Resend email page." width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm currently using Resend to notify me of people using the contact form on this website but I'm planning to use it and React Email on some other projects. You can view &lt;a href="https://github.com/timveletta/timveletta-com/blob/main/api/contact.js"&gt;my contact form function on Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>resend</category>
      <category>emails</category>
    </item>
    <item>
      <title>Why I use SaaS products instead of cloud providers</title>
      <dc:creator>Tim Veletta</dc:creator>
      <pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/timmahh/why-i-use-saas-products-instead-of-cloud-providers-4f9c</link>
      <guid>https://dev.to/timmahh/why-i-use-saas-products-instead-of-cloud-providers-4f9c</guid>
      <description>&lt;p&gt;For the past couple of years, I’ve been all in on AWS, I work with it every day and choose to use it for a number of my own projects on the side but lately, I have started to change. One of the main reasons for this is the time investment involved in keeping up to date with AWS services and learning new ones that help me achieve what I want. Also the documentation for each of these services can be a bit hit-or-miss and in a large number of cases there is a SaaS product out there that does a better job, in a better way.&lt;/p&gt;

&lt;p&gt;In this article I’m going to give a couple of examples of SaaS products I’d rather use than the comparable AWS service as an example of the types of products I would use to get a new side project off the ground and into production. A large number of my projects involve a few common elements including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosted front-end built with NextJS&lt;/li&gt;
&lt;li&gt;Serverless compute&lt;/li&gt;
&lt;li&gt;Data store&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I feel like it would be important to cover these elements below along with a few others that help make my job a bit easier and more enjoyable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted Front-End
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M-0BbD_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsut2jq732p3diken8fh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M-0BbD_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsut2jq732p3diken8fh.png" alt="Vercel logo" width="880" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here the choice is easy; &lt;strong&gt;Vercel&lt;/strong&gt; makes it so easy to connect a Github repository, deploy an application and manage the hosting. It also helps that NextJS is developed by Vercel but that doesn’t mean you cant easily deploy your Svelte, Vue or Astro sites there.&lt;/p&gt;

&lt;p&gt;If you were to try and tackle this in AWS you would have to configure Route53 to point to a Cloudfront distribution which would then point to an S3 bucket but even then you would only be able to manage static or client-rendered sites. I've shown an example of how to do this in my post on &lt;a href="https://timveletta.com/blog/scaffolding-your-aws-single-page-application-infrastructure-with-cdk"&gt;Scaffolding your AWS single page application infrastructure with CDK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s1eft2Iw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2l5lxt5vvu584g7kijdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s1eft2Iw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2l5lxt5vvu584g7kijdw.png" alt="CDK hosting infrastructure" width="880" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The general direction for React and other web frameworks is that they are moving more towards a server-rendered model where the server fetches the necessary data to render the page before sending it to the client. Doing it this way allows for faster initial loading of sites, a better user-experience and improved SEO ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Compute
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M-0BbD_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsut2jq732p3diken8fh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M-0BbD_P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsut2jq732p3diken8fh.png" alt="Vercel logo" width="880" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, the logical choice for this is &lt;strong&gt;Vercel&lt;/strong&gt; for many of the aforementioned reasons. Vercel Serverless Functions work much the same as AWS Lambda functions including automated scaling, multiple runtimes and the ability to be run at the edge.&lt;/p&gt;

&lt;p&gt;NextJS makes it easy to write Serverless Functions since its something that is baked into your project from the start with the &lt;code&gt;/api&lt;/code&gt; directory but you can still take advantage of them even if you’re not using NextJS.&lt;/p&gt;

&lt;p&gt;Although I’m opting to use Vercel for my serverless compute, I’m still a huge fan of AWS Lambda; its more because I’m using Vercel for my hosting that it makes sense for me to also use Serverless Functions because then I don’t have to mess around with AWS permissions and managing everything through Cloudformation, CDK, Terraform etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Store
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g2denY3B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89p05f2e3me0xebvd2d2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g2denY3B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89p05f2e3me0xebvd2d2.png" alt="PlanetScale logo" width="880" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honestly, I’ve never really gone deep on databases and have only ever used the solution that made it easy for me to get up and running with the least amount of pain. In the past I’ve used Firebase, Mongo and AWS DynamoDB (I really like &lt;a href="https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/"&gt;Single Table Design&lt;/a&gt;) because of their flexibility and ease of use but they’ve all had their pain points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PlanetScale&lt;/strong&gt; is the first database related service that I’m actually excited to use and learn more about. I really like their branching feature that allows you to manage your database like you would code including a Pull Request like flow to release changes to your database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p6g2z7X6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mg8bbzbkf1fotbzw4v9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p6g2z7X6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mg8bbzbkf1fotbzw4v9s.png" alt="PlanetScale branching" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m also a huge fan of using &lt;strong&gt;Prisma&lt;/strong&gt; as an Object-relational mapping (ORM) tool to manage my database schema and interactions from my serverless functions. The combination of Prisma and PlanetScale is super&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pzl2Y6jf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mgpgwypaft6qu50yqztf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pzl2Y6jf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mgpgwypaft6qu50yqztf.png" alt="Clerk logo" width="880" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could go any number of directions for authentication including Auth0 and Okta as an alternative to AWS Cognito as many others have done but I’ve been using &lt;strong&gt;Clerk&lt;/strong&gt; recently and have had nothing but good experiences.&lt;/p&gt;

&lt;p&gt;Clerk makes it very easy to add user sign up and authentication flows to your application including a large number of &lt;a href="https://clerk.dev/docs/authentication/social-connections-oauth"&gt;social connections&lt;/a&gt;. Clerk also integrates well with NextJS including helpers for both your front-end and serverless functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD
&lt;/h2&gt;

&lt;p&gt;Finally, because I’m using Github to manage my code repositories, it makes sense to use &lt;strong&gt;Github Actions&lt;/strong&gt; for CI/CD as opposed to AWS CodeBuild and CodePipeline. The big advantage Github Actions has is its integrations and community workflows which make it easy to leverage common, repeatable patterns that have been created by the community.&lt;/p&gt;

&lt;p&gt;One such example of a community workflow is &lt;code&gt;aws-actions/configure-aws-credentials&lt;/code&gt; which is used to configure your Github Actions to access AWS as seen in my article on &lt;a href="https://www.timveletta.com/blog/pushing-content-to-s3-from-github-actions"&gt;Pushing Content to AWS S3 from Github Actions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about IaC?
&lt;/h2&gt;

&lt;p&gt;Something that is notably missing from my recommendations above is an Infrastructure as Code (IaC) tool such as Terraform or AWS CloudFormation to manage your deployed resources in a scalable and repeatable way. Technically, my CI/CD is managed using IaC with Github Actions Workflows and my database schema is managed via Prisma but in the case of Vercel, Clerk and PlanetScale its click-ops all the way.&lt;/p&gt;

&lt;p&gt;Again, this comes down to time, learning difficulty and relying on the service providers to provide the service they advertise which in most cases is using AWS under-the-hood anyways.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the cost?
&lt;/h2&gt;

&lt;p&gt;Each of the services I’ve listed above have generous free tiers, something that AWS only offers within your first 12 months. The free tiers are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; offers free use for personal or non-commercial projects with up to 100GB of bandwidth so its perfect for putting together a proof of concept. After that its &lt;strong&gt;$20 per user / month&lt;/strong&gt; for up to 1TB of bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PlanetScale&lt;/strong&gt; free tier allows for up to 5GB of database storage and 1 billion row reads per month. Its then &lt;strong&gt;$29 per month for 10GB of storage&lt;/strong&gt; and &lt;strong&gt;$599 for 100GB of storage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clerk&lt;/strong&gt; allows up to 5,000 monthly active users (MAUs) on its free tier however it must include the Clerk branding. The remaining pricing structure confused me a bit; its &lt;strong&gt;$25 per month&lt;/strong&gt; to remove the branding but allows for only 1,000 MAUs + $0.02 per additional user meaning to hit your original 5,000 MAUs it will cost &lt;strong&gt;$105 per month&lt;/strong&gt;. Frustratingly, multi-factor authentication (MFA) is only available to you on the &lt;strong&gt;$99 per month&lt;/strong&gt; plan so you’ll be paying &lt;strong&gt;$179 per month&lt;/strong&gt; for your 5,000 MAUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Github Actions&lt;/strong&gt; offers 2,000 minutes and 500GB on its free tier and then $4 per user / month for up to 3,000 minutes and 1GB of storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of the free tiers are enough to get a proof-of-concept off the ground and test the market and if I ever get to the point where I’m worrying about exceeding 100GB of bandwidth on Vercel, 5GB of database storage on PlanetScale or even 1,000 MAUs on Clerk, I’m probably doing something right.&lt;/p&gt;

&lt;p&gt;If there ever comes a time where using one of these services becomes cost-prohibitive, replacing them with the equivalent AWS services might be worth my time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus Advice
&lt;/h2&gt;

&lt;p&gt;While I’m plugging services and products that I recommend, I should finish with one project I’ve extremely excited to be using and that is &lt;a href="https://create.t3.gg/"&gt;Create T3 App&lt;/a&gt;. This community project is a great starting point for building a full-stack, typesafe application using NextJS, TRPC, Prisma and Tailwind.&lt;/p&gt;

&lt;p&gt;The documentation around Create T3 App is amazing and by using the aforementioned SaaS products; you can get a full-stack application deployed in a short time so you can spend more time developing features that matter to your users.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>vercel</category>
      <category>planetscale</category>
      <category>clerk</category>
    </item>
    <item>
      <title>How to use Vercel Edge Config in your Functions</title>
      <dc:creator>Tim Veletta</dc:creator>
      <pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/timmahh/how-to-use-vercel-edge-config-in-your-functions-4ffp</link>
      <guid>https://dev.to/timmahh/how-to-use-vercel-edge-config-in-your-functions-4ffp</guid>
      <description>&lt;p&gt;I recently had to chance to use &lt;a href="https://vercel.com/blog/edge-config-ultra-low-latency-data-at-the-edge"&gt;Vercel Edge Config&lt;/a&gt; for a project where I needed to manage a small amount of key-value data and didn't want to go down the path of setting up database infrastructure to manage it. If you're like me and haven't heard about Edge Config, its &lt;em&gt;"a key-value data store (...) [that] enables you to read data at the edge without querying an external database"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This comes in useful for things such as setting up feature flags without requiring a redeployment, A/B testing or, in my case, managing API authorisation tokens. You can find out more about my particular use case in my previous post on &lt;a href="//tracking-my-progress-on-my-2023-goals.md"&gt;Tracking progress on my 2023 goals&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before I get into how to use Edge Config, there are some important things that are important to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The store is associated with your Vercel account as opposed to a particular project. This means you can share Edge Config between multiple projects or manage access on a project-by-project basis.&lt;/li&gt;
&lt;li&gt;Edge Config is very small. Plan limits range from 8 KB of storage for Hobby accounts to 512 KB for Enterprise&lt;/li&gt;
&lt;li&gt;It should only be used for data that is read frequently and written to infrequently. This is because write operations can take up to 10 seconds to propagate globally so there is no guarantee of consistent reads.&lt;/li&gt;
&lt;li&gt;As of writing, the feature is currently in beta.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;To get started with Vercel Edge Config, you first need to enable Edge Config on your account by logging into your Vercel account and going to the "Edge Config" tab. From there it will prompt you to create your first store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xquyyL-S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqofgnqhtdrsdzbn434z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xquyyL-S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqofgnqhtdrsdzbn434z.png" alt="Edge Config create" width="880" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On creation, it will give you an example key-value pair which we are going to use for the rest of this article. Next, you will need to connect your project to the store. This can be done in the "Projects" menu. When you connect your project to the store, it will set the &lt;code&gt;EDGE_CONFIG&lt;/code&gt; environment variable which is a token that is used to authenticate with your Edge Config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZkIGgDSV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9agxoiwo75pj79komgx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZkIGgDSV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9agxoiwo75pj79komgx.png" alt="Edge Config connect" width="880" height="721"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading from Edge Config
&lt;/h2&gt;

&lt;p&gt;Reading from Edge Config is fairly easy if you're also using Vercel Functions. First, you will need to install &lt;code&gt;@vercel/edge-config&lt;/code&gt; as a dependency in your project.&lt;/p&gt;

&lt;p&gt;Then its just a case of using the &lt;code&gt;getAll&lt;/code&gt; function passing in a list of variables you would like to receive. If you omit the list, it will return all variables in the Edge Config. The &lt;code&gt;@vercel/edge-config&lt;/code&gt; package makes use of the &lt;code&gt;EDGE_CONFIG&lt;/code&gt; environment variable we created in the previous step so if you're having issues authenticating, that is the first place to look.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAll&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;@vercel/edge-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="p"&gt;}&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;getAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greeting&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;h2&gt;
  
  
  Writing to Edge Config
&lt;/h2&gt;

&lt;p&gt;The easiest way to update the Edge Config is through the Vercel Dashboard however you'll often need to update it through your Vercel Functions. The easiest way to do this is to make a &lt;code&gt;PATCH&lt;/code&gt; request to your Edge Config URL which will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.vercel.com/v1/edge-config/your_edge_config_id_here/items

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

&lt;/div&gt;



&lt;p&gt;The body of your request should be a list of items which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;operation&lt;/code&gt; - either &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt; or &lt;code&gt;upsert&lt;/code&gt; which creates the item if it doesn't exist and updates it if it does&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;key&lt;/code&gt; - the key of the item you want to perform the operation on&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;value&lt;/code&gt; - the value of the item which can only be a &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;object&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt; or an array of the previous types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make the request, you will also need to create a Vercel API Access Token using &lt;a href="https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token"&gt;these instructions&lt;/a&gt; and add it to your project environment variables as &lt;code&gt;VERCEL_ACCESS_TOKEN&lt;/code&gt; (or any other descriptive name) so we can use it in the request headers. Note that if when you update your project environment variables, they won't take effect until you redeploy your app.&lt;/p&gt;

&lt;p&gt;Another important thing to note is that you must also specify &lt;code&gt;'content-type': 'application/json'&lt;/code&gt; in your headers or you'll get an error message saying &lt;code&gt;"Unsupported Media Type"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Upon making the request; you can check the &lt;code&gt;status&lt;/code&gt; parameter in the result to see if all the operations you performed were successful as shown in the following example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.vercel.com/v1/edge-config/your_edge_config_id_here/items&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;VERCEL_ACCESS_TOKEN&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello vercel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to update edge config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;If you're already using Vercel to host your site, I'd highly recommend giving Edge Config a try if you need a small amount of key-value storage without having to set up your own database infrastructure.&lt;/p&gt;

</description>
      <category>vercel</category>
    </item>
    <item>
      <title>Tracking progress on my 2023 goals</title>
      <dc:creator>Tim Veletta</dc:creator>
      <pubDate>Fri, 27 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/timmahh/tracking-progress-on-my-2023-goals-h53</link>
      <guid>https://dev.to/timmahh/tracking-progress-on-my-2023-goals-h53</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; I built a dashboard that tracks my goals for 2023 which you can &lt;a href="https://dev.to/2023-goals"&gt;view here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each year I tend to set myself some goals; usually not very specific ones such as listen to more music, play more games or get fitter. Each year, the vague definition of the goal leads it to becoming something that fades in and out of my consciousness during the year and it becomes something that is measured by gut feel. If I've learnt anything from the company I work for, &lt;a href="https://www.youtube.com/watch?v=tTpCfwyQt5c" rel="noopener noreferrer"&gt;Mechanical Rock&lt;/a&gt;, its that you should measure what matters which means setting specific, measurable goals.&lt;/p&gt;

&lt;p&gt;In setting some goals for this year, I thought it might also be a good experience to build a dashboard that tracks the progress on my goals throughout the year, more for my own use but also for some sense of public accountability. My goals for 2023 are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write 24 blog posts&lt;/strong&gt; - I enjoy writing however I tend to push it down the list of priorities even though I have a long list of things to write about. I chose 24 because it feels manageable to break it down to 2 blog posts per month and I can track it easily within my own site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read 24 books&lt;/strong&gt; - again, something I enjoy but gets pushed down the list of priorities and again, 2 books per month feels manageable. Its something I assumed I could easily track using &lt;a href="https://www.goodreads.com/user/show/151018120-timothy-veletta" rel="noopener noreferrer"&gt;my Goodreads account&lt;/a&gt; but since they &lt;a href="https://help.goodreads.com/s/article/Does-Goodreads-support-the-use-of-APIs" rel="noopener noreferrer"&gt;deprecated their API&lt;/a&gt; it was not so easy as I'll explain below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Play all the board games in my collection&lt;/strong&gt; - I have a collection of 100 board games at time of writing (as shown below) and there are a number of games that I haven't got to the table in quite some time. I would like to play all 100 games plus any new ones I acquire over the course of the year and I can track this through a site called &lt;a href="https://boardgamegeek.com/user/timmahh" rel="noopener noreferrer"&gt;Board Game Geek&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cycle over 1,200km&lt;/strong&gt; - as much as I enjoy it, I haven't found as much time for cycling in the past year because of other sporting commitments but now that those have finished, I would like to get back into it. 1,200km sounds like a lot but breaking it down, its 100km a month or cycling to work and home 3 days a month which I can track using &lt;a href="https://www.strava.com/athletes/64997285" rel="noopener noreferrer"&gt;Strava&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%2Fl13zgybc1wegzqajze1j.jpg" 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%2Fl13zgybc1wegzqajze1j.jpg" alt="My board game collection." width="800" height="974"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By integrating each of the services I mentioned above, I was able to build a dashboard that tracks my progress on each of my goals which is &lt;a href="https://dev.to/2023-goals"&gt;available here&lt;/a&gt;. I'm going to dive a bit further into how I integrated each of the services in the rest of this post.&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%2Fo0fllmnp3idmp33esy4c.jpg" 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%2Fo0fllmnp3idmp33esy4c.jpg" alt="The goals dashboard." width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching number of blog posts written
&lt;/h2&gt;

&lt;p&gt;Because my website uses &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, this was fairly straightforward. The snippet below gets all the Markdown files in my &lt;code&gt;/blog&lt;/code&gt; directory and filters them based on whether they were released in 2023.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsThisYear&lt;/span&gt; &lt;span class="o"&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;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./blog/*.{md,mdx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2023&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finding out how many books I've read from Goodreads
&lt;/h2&gt;

&lt;p&gt;As I mentioned before, Goodreads deprecated their API and as far as I found, there was nothing to replace it so the next best solution was scraping some data from my Goodreads profile.&lt;/p&gt;

&lt;p&gt;First, I made my profile public and started a reading challenge which generates a new page to track your progress against your reading challenge.&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%2Fsm979gxcilal3wh0wchq.jpg" 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%2Fsm979gxcilal3wh0wchq.jpg" alt="The Goodreads reading challenge page." width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then created a Vercel function which opens that page, parses the HTML result using &lt;code&gt;node-html-parser&lt;/code&gt;, searches the page for the challenge progress text and gets the first number in there which will be the number of books I've read so far in 2023.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.goodreads.com/user_challenges/41139180&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;html&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;root&lt;/span&gt; &lt;span class="o"&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;html&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;progressText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.progressText&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;text&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;numBooksRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progressText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+/&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tracking the games I've played with Board Game Geek
&lt;/h2&gt;

&lt;p&gt;Fortunately for me, Board Game Geek has &lt;a href="https://boardgamegeek.com/wiki/page/BGG_XML_API2" rel="noopener noreferrer"&gt;an API&lt;/a&gt; where the request parameters are reasonably well documented so I knew I had to make 2 requests, one to get all the games in my collection and another to get all the games I've played this year.&lt;/p&gt;

&lt;p&gt;Again, I created a Vercel function to manage this and started by fetching all the games that I "Own". Unfortunately, the API response is entirely in XML so I imported the &lt;code&gt;DOMParser&lt;/code&gt; from &lt;code&gt;jsdom&lt;/code&gt; to do most of the heavy lifting around navigating the XML and settled on the function below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchCollection&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;result&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://boardgamegeek.com/xmlapi2/collection?username=timmahh&amp;amp;own=1&amp;amp;brief&amp;amp;subtype=boardgame&amp;amp;excludesubtype=boardgameexpansion&lt;/span&gt;&lt;span class="dl"&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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="mi"&gt;2000&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;fetchCollection&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;xml&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;parser&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;DOMParser&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;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/xml&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;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;item&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&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;One thing to note, sometimes the BGG API returns with a status code of &lt;code&gt;202&lt;/code&gt; which means it has queued your request and that you should make another request after a short delay.&lt;/p&gt;

&lt;p&gt;Fetching the games I've played this year is largely similar, just with a different API endpoint so I won't show it here. Once I had both my collection and plays, it was just a case of iterating through the games in my collection and checking if my list of plays includes that game.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gamesPlayed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;game&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;plays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;game&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working out how far I've cycled with Strava
&lt;/h2&gt;

&lt;p&gt;For my final goal, I was able to use the &lt;a href="https://developers.strava.com/" rel="noopener noreferrer"&gt;Strava API&lt;/a&gt; to get my cycling stats however, the way they manage authorisation made this more difficult than I would have liked.&lt;/p&gt;

&lt;p&gt;Following the instructions on their &lt;a href="https://developers.strava.com/docs/getting-started/" rel="noopener noreferrer"&gt;Getting Started page&lt;/a&gt; I created an application and authorised my Strava data to be accessed by my application. From there I was able to get my access and refresh tokens to authenticate with the API however I needed somewhere to store these.&lt;/p&gt;

&lt;p&gt;I really didn't want to go down the path of setting up a whole database just to store 3 values so I used &lt;a href="https://vercel.com/docs/concepts/edge-network/edge-config" rel="noopener noreferrer"&gt;Vercel Edge Config&lt;/a&gt; to manage my tokens. Edge Config is a key-value data store that allows you to read data at the edge with negligible latency and is easily accessed from your Vercel functions. I would like to do another blog post in the future specifically on Edge Config because the documentation around it wasn't great and I feel like having a working example would have helped me greatly.&lt;/p&gt;

&lt;p&gt;Once I had solved the issue of authentication and authorisation; the &lt;code&gt;/athletes/{id}/stats&lt;/code&gt; endpoint returns a value for &lt;code&gt;ytd_ride_totals&lt;/code&gt; which is exactly what I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the page to update
&lt;/h2&gt;

&lt;p&gt;Because my site is built with Astro and set up as a static site on Vercel; having the page update was my final consideration. I really don't need "to-the-second" updates each time someone accesses the page because there normally isn't much of a change from one day to the next.&lt;/p&gt;

&lt;p&gt;In this case, it made sense to have the page update once per day so I set up a Github Action to run on a schedule that pings my Vercel deploy hook which triggers a build as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;⏰ Scheduled Build&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;🔫 Trigger site build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;♻️ Request to Vercel Deploy Hook&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -X POST -d {} ${{ secrets.VERCEL_DEPLOY_HOOK }}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;So there you have it, a bit of an insight into how I'm tracking my goals for 2023. I can't wait to see whether the visualisation of my goals has any effect on how I progress against them. Thanks for reading!&lt;/p&gt;

</description>
      <category>personal</category>
      <category>boardgames</category>
      <category>fitness</category>
      <category>books</category>
    </item>
    <item>
      <title>UPDATED: Deploying a static site to AWS using GitHub Actions</title>
      <dc:creator>Tim Veletta</dc:creator>
      <pubDate>Sun, 16 Aug 2020 13:32:54 +0000</pubDate>
      <link>https://dev.to/timmahh/updated-deploying-a-static-site-to-aws-using-github-actions-4np6</link>
      <guid>https://dev.to/timmahh/updated-deploying-a-static-site-to-aws-using-github-actions-4np6</guid>
      <description>&lt;p&gt;It has been just under a year since I wrote &lt;a href="https://www.timveletta.com/blog/2020-07-08-deploying-a-static-site-to-aws-using-github-actions/"&gt;my initial blog post&lt;/a&gt; about deploying static sites to AWS via GitHub Actions and a number of things have changed since then. Github Actions has now been generally available for some time and although number of the referenced actions have changed, the main principles remain the same. In this post I'll explain my process for deploying static sites to AWS using Github Actions in greater detail and whats changed since the initial version of the article.&lt;/p&gt;

&lt;p&gt;If you're just interested in the build template, it is posted in its entirety at the end of the article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assumed infrastructure
&lt;/h2&gt;

&lt;p&gt;The template assumes you are using a &lt;strong&gt;privately hosted&lt;/strong&gt; S3 static site and serving the content using CloudFront. You could just as easily use a &lt;strong&gt;public&lt;/strong&gt; S3 static site and omit the &lt;code&gt;Invalidate Cloudfront CDN&lt;/code&gt; step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9AuiUVp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vn1hj1lp68lt2binc98q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9AuiUVp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vn1hj1lp68lt2binc98q.png" alt="Assumed infrastructure layout" title="Assumed infrastructure layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking down the build template
&lt;/h2&gt;

&lt;p&gt;One of the more conventional approaches to continuous integration builds is breaking it down into 3 distinct steps, &lt;em&gt;test, build&lt;/em&gt; and &lt;em&gt;deploy.&lt;/em&gt; Doing so allows us to easily pinpoint at a glance where things have gone wrong when the build fails. In my previous build template, I had combined the build and deploy steps because at that point it was unclear how to share assets between jobs. However this changed upon discovering the &lt;code&gt;actions/upload-artifact&lt;/code&gt; and &lt;code&gt;actions/download-artifact&lt;/code&gt; actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;test&lt;/code&gt; job
&lt;/h2&gt;

&lt;p&gt;The test job is fairly straightforward, it simply checks out the code, installs the dependencies and runs the tests. Not much to see here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run developer tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  The &lt;code&gt;build&lt;/code&gt; job
&lt;/h2&gt;

&lt;p&gt;This is one of the differences from the previous article, I am splitting up the &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt; jobs since I now know how to manage build artifacts between steps. In this step, I again check out the code and install dependencies then follow it by building the site. Next, I upload the build artifact to GitHub using the &lt;code&gt;actions/upload-artifact&lt;/code&gt; action specifying the &lt;code&gt;build&lt;/code&gt; folder as our source and naming it &lt;code&gt;frontend-artifact&lt;/code&gt;. This name will be used later on as well as showing in the GitHub interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend-artifact&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--srNwU9mL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4kx2d1hsn2zcwmhsw1xq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--srNwU9mL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4kx2d1hsn2zcwmhsw1xq.png" alt="The frontend artifact showing in the GitHub interface" title="The frontend artifact showing in the GitHub interface"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;deploy&lt;/code&gt; job
&lt;/h2&gt;

&lt;p&gt;The deploy job is the other big difference from the previous article since it now uses the &lt;code&gt;aws-actions/configure-aws-credentials&lt;/code&gt; action to authenticate with AWS and then perform operations. Firstly, I set 2 conditions for running the &lt;code&gt;deploy&lt;/code&gt; job; that we only run on the &lt;code&gt;master&lt;/code&gt; branch and not on other branches, and we only run after successfully running the &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; jobs. This is simply because I only want to deploy code I am happy to merge into the main branch which has passed all the tests and successfully builds.&lt;/p&gt;

&lt;p&gt;I start by downloading the &lt;code&gt;frontend-artifact&lt;/code&gt; that I uploaded in the &lt;code&gt;build&lt;/code&gt; step, then I configure my access to AWS using GitHub secrets (see &lt;em&gt;Keeping your secrets safe&lt;/em&gt; in the previous article) before uploading the code to S3 and invalidating the CloudFront cache so that it knows about the most recent version of the site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/master'&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend-artifact&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;YOUR REGION HERE&amp;gt;&amp;gt;&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to S3 Bucket&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync ./ s3://&amp;lt;&amp;lt;YOUR S3 BUCKET NAME HERE&amp;gt;&amp;gt;&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invalidate Cloudfront CDN&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id=$CLOUDFRONT_DISTRIBUTION_ID --paths '/*'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CLOUDFRONT_DISTRIBUTION_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;YOUR CLOUDFRONT DISTRIBUTION ID HERE&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;So thats all the individual build jobs and a bit of a breakdown of each, the full template is below. There are 2 points worth mentioning about the full template; we are running it on each &lt;em&gt;push&lt;/em&gt; and &lt;em&gt;pull request&lt;/em&gt; so that we are informed of any issues sooner and the &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; steps run concurrently despite having to install dependencies each time. The reason for this is simply to get quick feedback and this way I don't have to deal with any form of hand off between each of the stages.&lt;/p&gt;

&lt;p&gt;I hope you've found this post useful and now have an idea of how easy it is to deploy you static sites to AWS using GitHub Actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Frontend CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run developer tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend-artifact&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/master'&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend-artifact&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;YOUR REGION HERE&amp;gt;&amp;gt;&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to S3 Bucket&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync ./ s3://&amp;lt;&amp;lt;YOUR S3 BUCKET NAME HERE&amp;gt;&amp;gt;&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invalidate Cloudfront CDN&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id=$CLOUDFRONT_DISTRIBUTION_ID --paths '/*'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CLOUDFRONT_DISTRIBUTION_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;YOUR CLOUDFRONT DISTRIBUTION ID HERE&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>aws</category>
      <category>githubactions</category>
      <category>continuousdelivery</category>
    </item>
  </channel>
</rss>
