<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Cup of Code</title>
    <description>The latest articles on Forem by Cup of Code (@cupofcode).</description>
    <link>https://forem.com/cupofcode</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%2F605875%2F3b406417-749e-4c23-a8c1-65185191b662.png</url>
      <title>Forem: Cup of Code</title>
      <link>https://forem.com/cupofcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cupofcode"/>
    <language>en</language>
    <item>
      <title>RSUs — What if you turned them into ETFs?</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sun, 01 Jun 2025 15:08:28 +0000</pubDate>
      <link>https://forem.com/cupofcode/rsus-what-if-you-turned-them-into-etfs-346e</link>
      <guid>https://forem.com/cupofcode/rsus-what-if-you-turned-them-into-etfs-346e</guid>
      <description>&lt;h1&gt;
  
  
  Thinking of cashing out the RSUs and investing the money elsewhere? Let’s run the numbers first!
&lt;/h1&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%2Fv28siaww3xchdebq61e8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv28siaww3xchdebq61e8.png" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://cupofcode.blog/intro-to-rsus/" rel="noopener noreferrer"&gt;the previous blog post&lt;/a&gt;, we talked about the RSUs portion of an employee’s compensation. At first, they are granted to you, and when the time comes, they are vested: The shares are yours!&lt;/p&gt;

&lt;p&gt;Once you can vest your shares, you need to pay income tax on them. For example, if you get 10 shares worth $100 each at the time of vesting, you need to pay 52% of their value to taxes (that number depends on where you live). This means that for those 10 shares, you need to pay $520 in taxes.&lt;/p&gt;

&lt;p&gt;We compared three options to handle the stocks given to you by the company:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sell all when received: sell all 10 shares and take $480 home.&lt;/li&gt;
&lt;li&gt;Sell to cover taxes: sell 6 shares, invest 4 shares, and take $80 home.&lt;/li&gt;
&lt;li&gt;Keep all: add $520 to your brokerage account before the shares vest, and then invest 10 shares.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find the full comparison of these options &lt;a href="https://cupofcode.blog/intro-to-rsus/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, we will explore a fourth option: sell all and direct the money towards an investment of your choice!&lt;/p&gt;

&lt;p&gt;Remember Aoife? She works at a company called &lt;a href="https://www.wonderslist.com/top-10-largest-rivers/#h-2-nile-river" rel="noopener noreferrer"&gt;Nile&lt;/a&gt;, and as part of her compensation plan, she received 20 RSUs in 2020 that were vested throughout the years. Somewhere after January 2022, her company did a 1-to-20 split of stocks, so the original 4 shares vesting became 80 smaller shares.&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%2Fbvxmkrv2ilt9k1t7xdxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvxmkrv2ilt9k1t7xdxp.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In today’s blog post, I’ll present the profits that Aoife would’ve gained if she were to invest the extra income in S&amp;amp;P 500 ETFs and sell in January 2025. Aoife might not be real, but the numbers are!&lt;/p&gt;

&lt;p&gt;Before we pull out the calculators, we need to review some popular terms from the investment world.&lt;/p&gt;

&lt;h2&gt;
  
  
  S&amp;amp;P 500
&lt;/h2&gt;

&lt;p&gt;The Standard and Poor’s 500, or simply the S&amp;amp;P 500, is a stock market index tracking the stock performance of 500 leading companies listed on stock exchanges in the United States. This is one of the first terms you hear when you start looking into investments because the funds tracking it are considered to be a beginner-friendly, safe option due to its broader scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  ETFs
&lt;/h2&gt;

&lt;p&gt;An ETF, or exchange-traded fund (ETF), is an investment fund that allows investors to purchase a variety of different stocks and/or bonds at once. In our case, our ETF is tracking the S&amp;amp;P 500 index. Like with other competitive markets, there are several ETF providers you can choose from.&lt;/p&gt;

&lt;p&gt;In the screenshot below, I attached the graphs of 4 different ETFs tracking the S&amp;amp;P 500. You can see they have similar trends, meaning they are all tracking the index pretty accurately! On the left, I have listed the top 8 companies, including the weight in %, represented in the Vanguard S&amp;amp;P 500 ETF.&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%2Frbl8gst3yvi96fkcku9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbl8gst3yvi96fkcku9a.png" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To explore this new scenario, Aoife will cash out her RSUs and invest that money in the S&amp;amp;P 500 ETF.&lt;/p&gt;

&lt;p&gt;Note that there are three important differences between investing in the S&amp;amp;P 500 ETF shares and investing in Nile shares:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The S&amp;amp;P 500 ETF holds multiple stocks: Nile is one of those 500 companies, representing ~&lt;a href="https://www.slickcharts.com/sp500" rel="noopener noreferrer"&gt;3.85%&lt;/a&gt; of those assets. So yes, she is selling Nile stocks and later purchasing some Nile stocks.&lt;/li&gt;
&lt;li&gt;You can purchase fractional ETF shares. In the previous blog post, when Aoife was selling to cover taxes, she was sometimes left with cash. That’s because you can’t buy a fraction of a stock. But with ETFs, you don’t need to reach the whole value of a share to purchase!&lt;/li&gt;
&lt;li&gt;If you couldn’t tell by her name, Aoife is an Irish resident, and therefore, her ETF’s capital gain is taxed at 41%, unlike the 33% that stocks are taxed at. We will calculate that into the formula.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we start, note that we are comparing stock profit to ETF profit. Meaning, in a normal setting, Aoife would’ve invested all the cash received from selling the RSUs. For our experiment, we will only invest the money that was invested in the stocks, and any leftover cash (that didn’t accumulate to a full stock price) won’t be invested in ETFs either.&lt;/p&gt;

&lt;p&gt;Now we can start calculating :D&lt;/p&gt;




&lt;p&gt;Let’s look at the S&amp;amp;P 500 index in the last 5 years (&lt;a href="https://www.google.com/finance/quote/.INX:INDEXSP?window=MAX" rel="noopener noreferrer"&gt;link&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;The table on the right shows us the amount of money invested in Nile shares by vesting period.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;January 2021: Aoife got her first share! But she had to sell it to cover taxes. As mentioned earlier, this means we won’t be investing in any ETFs this time.&lt;/li&gt;
&lt;li&gt;January 2022: 1 Share vested, worth $3240, so we put $3240 in our ETF!&lt;/li&gt;
&lt;li&gt;July 2022: 38 shares (post 1-to-20 share split) worth $110 each, meaning $4180 goes to ETFs!&lt;/li&gt;
&lt;li&gt;January 2023: 38 shares worth $86 each, meaning $3238 goes to ETFs!&lt;/li&gt;
&lt;li&gt;July 2023: 38 shares worth $130 each, meaning $4940 goes to ETFs!&lt;/li&gt;
&lt;li&gt;January 2024: 38 shares worth $145 each, meaning $3800 goes to ETFs!&lt;/li&gt;
&lt;li&gt;January 2025: Selling the ETF shares. But how much did Aoife gain?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We will calculate the Capital Gain from each batch of purchase:&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%2Fx12dknojm09fw7h8goi1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx12dknojm09fw7h8goi1.png" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike stocks, because we buy fractional ETF shares, you can’t just multiply the number of shares by the delta between the buy and sell values. Instead, I used a different method: In the &lt;a href="https://www.google.com/finance/quote/.INX:INDEXSP?window=MAX" rel="noopener noreferrer"&gt;link&lt;/a&gt; I shared earlier, you can get the percentage of value difference in a certain period.&lt;/p&gt;

&lt;p&gt;For example, from January 2022 to January 2025, there was a 27% increase in value. This means the capital gain was 3240*0.27= $874.8. Now, because the capital gain tax in Ireland is 41%, Aoife’s profit is 874.8*0.59= $516.1.&lt;/p&gt;

&lt;p&gt;Now it’s time to compare! If Aoife were to take the stock route, she would’ve made $33942! This number is a bit lower than the one presented in &lt;a href="https://cupofcode.blog/intro-to-rsus/" rel="noopener noreferrer"&gt;the previous blog post&lt;/a&gt;, because this does not include the leftover cash from covering taxes in any of the vestings.&lt;/p&gt;

&lt;p&gt;If Aoife were to invest her RSUs money in an ETF, she would’ve made $25878! This is a $8064 difference!!&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%2Fz00zekmyuo1vi2i30taz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz00zekmyuo1vi2i30taz.png" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two reasons for this big gap:&lt;/p&gt;

&lt;p&gt;First of all, the tax on ETFs is 8% more than on stocks. Second, the S&amp;amp;P 500 tracks 500 companies, so it’s more stable than an individual stock. Some shares go up more, some go down, and they balance themselves. The value of the ETF share increased by 27–55%, while the individual stock’s value fluctuated between 44%-172%! Of course, this is nice when you buy the share at $86 and sell at $235, but not so much when it’s the other way around.&lt;/p&gt;

&lt;p&gt;As I concluded in the previous blog post, the stock market is kind of a gamble. I once heard some advice: “Don’t invest money you are not willing to lose”. At first I didn’t get it — I mind every money loss! But after a while, I understood: They mean you shouldn’t invest money you can’t afford to lose. So it shouldn’t be any money for a nearby purpose: emergency fund, vacation, wedding, house, etc. Be responsible!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;This is not financial advice&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fzcwq4sgg1cw5xlgxn221.png" width="700" height="175"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cupofcode.blog/" rel="noopener noreferrer"&gt;https://cupofcode.blog/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :) Thank you for your support!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understand Your Paycheck: RSUs</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Mon, 03 Feb 2025 12:50:05 +0000</pubDate>
      <link>https://forem.com/cupofcode/understand-your-paycheck-rsus-4606</link>
      <guid>https://forem.com/cupofcode/understand-your-paycheck-rsus-4606</guid>
      <description>&lt;h3&gt;
  
  
  Explaining the terms and simplifying the calculation process — with a real-life example!
&lt;/h3&gt;

&lt;p&gt;If you’ve been ignoring the RSUs part in your paycheck — This blog post is for you! I have a walkthrough of all the calculations :D&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%2Fom3vq4lxeb2jef8pgyjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom3vq4lxeb2jef8pgyjw.png" alt="main-photo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have a confession to make: I used to disregard the RSUs part of my compensation. It’s not “real” money coming into my checking account, so it doesn’t count. Recently, I decided to learn more about personal finances and gained a new appreciation for them!&lt;/p&gt;

&lt;p&gt;Unfortunately, the stock portion of your compensation is not straightforward: RSUs vs shares, vesting options, taxed once, taxed twice…With all that, how much money do you end up with? This is a complex calculation, that I’m here to help with!&lt;/p&gt;

&lt;p&gt;We’ll start with some general information, and then go through a &lt;strong&gt;real-life example&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Of course, nothing here is financial advice. I’m a software developer, not an accountant. Also, let me clarify — I am only using information available on the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are RSUs?
&lt;/h2&gt;

&lt;p&gt;RSUs are Restricted Stock Units and are a common part of the employee’s compensation. They are called &lt;em&gt;restricted&lt;/em&gt; because it is not fully transferable until certain conditions have been met (like tenure).&lt;/p&gt;

&lt;p&gt;RSUs are a retention tool for employers: The employees have an incentive to stay in the company longer until the next RSUs are vested.. and the next ones.. and the next ones. Also, the employee might perform better knowing the success of the company will lead to an increase in share value, which will be worth more money for them (compared to a fixed base salary that will come in regardless of the company’s performance).&lt;/p&gt;

&lt;p&gt;RSUs have a life cycle: First, they are &lt;strong&gt;granted&lt;/strong&gt; to you (saved for you, kind of like a reserved table at a restaurant), and when the time comes — they are &lt;strong&gt;vested&lt;/strong&gt;: The shares are yours! But… don’t get too comfortable, because you need to pay taxes on that income!&lt;/p&gt;

&lt;p&gt;(Note that I use the words “share” and “stock” to describe the same thing, but if you want to be precise — &lt;em&gt;“stock” is the financial instrument a company issues, and a “share” is a single instance of that financial instrument. — &lt;a href="https://www.investopedia.com/ask/answers/difference-between-shares-and-stocks/" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/em&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Vesting Options
&lt;/h3&gt;

&lt;p&gt;When it comes to vesting, there are three routes you can go: Selling all the new stocks (&lt;strong&gt;sell-for-profit&lt;/strong&gt;), selling just enough to cover their taxes (&lt;strong&gt;sell-to-cover&lt;/strong&gt;), or paying the taxes in advance (which are 52.1% today in Ireland). The choice depends on how much cash you can/will &lt;strong&gt;invest&lt;/strong&gt; in it and whether you &lt;strong&gt;think&lt;/strong&gt; the stock will increase in value. Some might prefer cashing the stocks and investing in other things by themselves. The default is sell-to-cover.&lt;/p&gt;

&lt;p&gt;Lastly, if you choose to keep some stocks and they gain profit — once you sell them — you need to pay the capital gain tax of 33% (yes, EVERYTHING is taxed).&lt;/p&gt;

&lt;h3&gt;
  
  
  Share split
&lt;/h3&gt;

&lt;p&gt;At some point, a company might decide to split the shares into smaller shares. In Amazon’s case, if you had a single share with a value of $3200 before the split — after the split you’ll own 20 shares of $160, which totals the same amount. That way, the shares are more affordable.&lt;/p&gt;

&lt;p&gt;If you check the history of a certain stock online, you will see only the new (smaller) value — which can confuse you when you check the history of your RSUs. Don’t worry, it will be clearer with a real-life example!&lt;/p&gt;

&lt;h3&gt;
  
  
  Brokerage account
&lt;/h3&gt;

&lt;p&gt;A brokerage account allows one to invest in publicly traded assets such as stocks. You probably can choose between brokerage companies your employer works with. Once you set up your vesting preferences, they will do the rest.&lt;/p&gt;

&lt;p&gt;There are two fees you should be aware of: The first is the Trading Commission, which anyone who trades pays, regardless of which broker they choose to go with. The second one is the Supplemental Transaction Fee, which is an additional fee you pay your broker.&lt;/p&gt;

&lt;p&gt;All the calculations we will see today do not include those fees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Life Example
&lt;/h2&gt;

&lt;p&gt;Meet my AI-generated friend, &lt;strong&gt;Aoife&lt;/strong&gt;. She started working in a company called &lt;strong&gt;Nile&lt;/strong&gt; 5 years ago (in January 2020). Her vesting approach from the beginning was to &lt;strong&gt;sell-to-cover&lt;/strong&gt;. Let’s help her calculate the “real money” that will come out from the RSUs &lt;strong&gt;granted&lt;/strong&gt; upon signing the contract if she were to sell them today (in 2025).&lt;/p&gt;

&lt;p&gt;Aoife signed a compensation plan containing 20 RSUs, and her employment started in January. In her contract, it says that &lt;em&gt;“RSUs follow a 4-year vesting schedule: 5% on the 15th day of the month in which you reach your first anniversary of employment, 15% on the 15th day of the month in which you reach your second anniversary of employment, and 20% every six months thereafter, until fully vested.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Therefore, in Aoife’s case:&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%2Fj5bpgeuzvepkhcv52rnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5bpgeuzvepkhcv52rnn.png" alt="meet Aoife" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*Note that Amazon’s RSUs (Oops! Nile’s RSUs, I mean) went through a 1 to 20 split in June 2022, which will make our calculation even more complex!&lt;/p&gt;

&lt;p&gt;Now, let’s add the value of the stock &lt;strong&gt;at the time&lt;/strong&gt; to each line. I’ll be rounding the values for easy calculation, but you can see the exact share value in your periodic stock documents. Note that the value is &lt;strong&gt;post-split!&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fgsev84i8jfe7csxaoai8.png" alt="Nile’s share value in the last 5 years" width="800" height="450"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nile’s share value in the last 5 years&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here are the numbers we are working with:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RSUs granted:&lt;br&gt;
January 2021: 1 (=20) RSU, share value: $160&lt;br&gt;
January 2022: 3 (=60) RSUs, share value: $162&lt;br&gt;
July 2022: 4 (=80) RSUs, share value: $110&lt;br&gt;
January 2023: 4 (=80) RSUs, share value: $86&lt;br&gt;
July 2023: 4 (=80) RSUs, share value: $130&lt;br&gt;
January 2024: 4 (=80) RSUs, share value: $145&lt;br&gt;
January 2025: ------, share value: $234 &amp;lt;- value when selling&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Pull Out Your Calculators!&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Each vesting has its calculation — different share values, different tax amounts to pay, and different shares left post-taxes. There are three cases I’d like to go over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Vesting (pre-split) where tax requires selling all shares.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vesting (pre-split) with shares left post taxes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vesting post-split.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lucky for us, these are the first three vestings (January 2021, January 2022, and July 2022). I will cover those scenarios within the blog post, and the rest of the calculation slides I’ll attach at the end of the blog post.&lt;/p&gt;

&lt;p&gt;Lastly, we will calculate the profit if Aoife were to sell all her shares in 2025.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. January 2021 — A Single Share Vesting
&lt;/h3&gt;

&lt;p&gt;Here we have 1 (pre-split) RSU, with a value of 160*20= $3200. Taxes for this income are 3200*0.52= $1664. Because Aoife is selling-to-cover + there is only 1 share = she sells the share and gets an extra 3200*0.48= &lt;strong&gt;$1536&lt;/strong&gt; in her next paycheck. By the way, she will also see a line in the deduction column named “&lt;em&gt;RSU TX WH&lt;/em&gt;” — which indicates money withholding for taxes from that vesting.&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%2Fl7pk7voxrf22ep6c0ivc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7pk7voxrf22ep6c0ivc.png" alt="January 2021" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that when Aoife logs into her brokerage account, she will see that out of 20 shares vested in January 2021 — all were sold, which doesn’t fit the sell-to-cover approach. If it was, she would’ve sold only 11 shares and get to keep 8. This mismatch is because the number of shares presented is &lt;strong&gt;post-split&lt;/strong&gt;, while the vesting was &lt;strong&gt;pre-split&lt;/strong&gt;. Very confusing, I know!&lt;/p&gt;

&lt;p&gt;Let’s review the other options for vesting on this example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Selling for profit: In this case, sell-for-profit = sell-to-cover, because there was only one share.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paying tax in advance: In that case, Aoife would’ve paid $1664 before the vesting, and then instead of getting $1536, she would’ve received 1 share.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. January 2022 — A Pre-Split Vesting
&lt;/h3&gt;

&lt;p&gt;Here we are still with the old shares. The value of the income (the granted 3 stocks) is 3*20*162= $9720. Therefore, income tax is 9720*0.52= $5054.4. Because Aoife is selling-to-cover, she has to sell 2/3 of her shares: This gives her 2*20*162=$6480. Lastly, after covering taxes, she is left with 6480-5054.4= $1425.6 extra cash in her next payslip.&lt;/p&gt;

&lt;p&gt;To conclude, in January 2022, Aoife gets &lt;strong&gt;1 share&lt;/strong&gt; (pre-split, worth $3240) and &lt;strong&gt;$1425.6&lt;/strong&gt; cash.&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%2Fe92z1w3dlhqreimegxom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe92z1w3dlhqreimegxom.png" alt="January 2022" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here as well, when Aoife logs into her brokerage account, she will see that out of the 80 shares she received in January 2022–60 were sold, which doesn’t fit the sell-to-cover approach. If it was, she would’ve sold only 42 shares and get to keep 38. This mismatch is because the number of shares presented is &lt;strong&gt;post-split&lt;/strong&gt;, while the vesting was &lt;strong&gt;pre-split&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s review the other options of vesting on this example (just to ensure we understand!):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sell-for-profit: In this case, Aoife would’ve gotten 9720 * 0.48 = $4665.6 and &lt;strong&gt;0&lt;/strong&gt; shares.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paying tax in advance: In that case, Aoife would’ve needed to pay $5054.4 from her own money, and then she would’ve gained &lt;strong&gt;3&lt;/strong&gt; shares.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. July 2022— A Post-Split Vesting
&lt;/h3&gt;

&lt;p&gt;This is the first vesting post-split! Originally Aoife was granted 4 RSUs with the original value, but after the split, those 4 shares became 80 shares, with the total value remaining the same: 80*110= $8800.&lt;/p&gt;

&lt;p&gt;With smaller shares, we can sell-to-cover taxes more accurately — leaving us with more shares and less cash: Taxes for this income are 8800*0.52= $4576. To cover taxes, Aoife needs to sell 4576/110=41.6 -&amp;gt; 42 shares. Therefore, out of the 80 shares — Aoife keeps 38 shares, pays $4576 tax, and gets 42*110-4576= $44 extra cash in her next paycheck.&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%2F8cl3spkc3x0ahm2wjq3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cl3spkc3x0ahm2wjq3u.png" alt="July 2022" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was the calculation of the third vesting out of six. You can find the calculation slides for the rest of the vestings in the appendix of the blog post.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. January 2025 — Selling All Shares
&lt;/h3&gt;

&lt;p&gt;Ok, now Aoife wants to sell her stocks! She checked the current share value, and was happy to see that it increased since her RSUs vested! But… how much cash will she get? Let’s calculate!&lt;/p&gt;

&lt;p&gt;For each vesting period, we need to calculate the difference between the total shares value on vesting and at the time of selling ($234 at the time of writing). That difference is the profit — and Aoife needs to pay capital gain tax on that.&lt;/p&gt;

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

&lt;p&gt;Note that there are only 5 vestings in the image above, but there were 6 vesting dates. That is because the first vesting (January 2021) didn’t result in any shares.&lt;/p&gt;

&lt;p&gt;Let’s look at the numbers: Aoife accumulated 172 shares that at the time of selling are worth 172*234= $40248. Out of that, 3240+4180+3268+4940+5510= $21138 is income Aoife already paid tax on because that was the value at the time of vesting. That’s what I call “Cash from original value”. The profit from the shares invested is 40248-21138= $19110. This amount is the capital gain — which requires a tax of 19110*0.33= $6306 (I rounded down), leaving us with 19110-63006= $12804.&lt;/p&gt;

&lt;p&gt;Therefore, the total amount of cash entering Aoife’s account is 21138+12804= $33942.&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%2Fq8ghq56nh8j0iumw8zf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8ghq56nh8j0iumw8zf9.png" alt="selling in 2025 graph" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  To Conclude
&lt;/h3&gt;

&lt;p&gt;Now, we can take all the cash earned over all the years, and sum it up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Ff4rld6wydknlvuzaycpb.png" alt="Look how much Aoife paid in taxes!" width="800" height="450"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Look how much Aoife paid in taxes!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now, Aoife is wondering what would the profit be if she were to choose another vesting option:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sell-all is easy to calculate: the value of shares vested * 0.48 (assuming 52% taxes). This means there is no income in 2025.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep-all calculation: keep the original number of shares vested, and extract the cash paid to cover taxes from the gain made in 2025.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2F13somzswgcx3rw0fz620.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13somzswgcx3rw0fz620.png" alt="compare compensation plans" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that in Aoife’s case, if she were to not invest the cash in anything else in the “sell-all” option, and if Nile’s share value goes as in the example: keep all would’ve been the most profitable option.&lt;/p&gt;

&lt;p&gt;Buying shares is a &lt;strong&gt;gamble&lt;/strong&gt; because you don’t know if they will go up or down &lt;strong&gt;when you need to cash them out&lt;/strong&gt;. Therefore, there is no “best approach” with your RSUs: Do what fits you, your financial situation, and your comfort (enjoyment?) with risk-taking.&lt;/p&gt;

&lt;p&gt;For your convenience, I created a Google sheet with Aoife’s RSU calculations: you can make a copy and put your numbers in!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fl1o3gc3qg81g7dme4opd.png" alt="cupofcode_blog_rsus_sheet" width="800" height="450"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://bit.ly/4aFInOQ" rel="noopener noreferrer"&gt;https://bit.ly/4aFInOQ&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That’s it for today! Interested in digging more into your paycheck? Check out &lt;a href="https://cupofcode.blog/pdf-payslips-report-script/" rel="noopener noreferrer"&gt;my blog post about Turning PDF Payslips Into a Single CSV Report&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiw809sodqfga2uycepbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiw809sodqfga2uycepbz.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :) Thank you for your support!&lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix
&lt;/h3&gt;

&lt;p&gt;The slides for the rest of the vesting calculations:&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%2F1bwmn94lrm3tl8td1k2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bwmn94lrm3tl8td1k2v.png" alt="January 2023" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

</description>
    </item>
    <item>
      <title>Turn PDF Payslips Into a Single CSV Report</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sun, 22 Dec 2024 21:03:27 +0000</pubDate>
      <link>https://forem.com/cupofcode/weekend-coding-turn-pdf-payslips-into-a-single-csv-report-4212</link>
      <guid>https://forem.com/cupofcode/weekend-coding-turn-pdf-payslips-into-a-single-csv-report-4212</guid>
      <description>&lt;h3&gt;
  
  
  Ever programmed with PDF files? Write a Python script with me!
&lt;/h3&gt;

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

&lt;p&gt;As you will see in the upcoming blog posts, I am in my financial literacy era. With the end of the year coming, I wanted to look at my numbers: How much taxes did I pay? How much did I earn for on-call shifts? Multiple PDF files are not the most comfortable way to see this data, and I wanted a single CSV file I could play with in Excel.&lt;/p&gt;

&lt;p&gt;Like many good developers, I was too lazy to insert the numbers manually, so I wrote a script. If you enjoy programming — join me on an adventure! And if you are not in the mood — I’ll show you how to tweak the code to match your payslip structure :D&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2F62k38nmm8uidfp5cj9ec.png" alt="illustration" width="800" height="445"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;This script receives a directory with payslip PDFs and returns a CSV file with the desired data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Plan
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main.py:
# translate 1 pdf to 1 dict
# loop over the pdf dir
# save all dicts to 1 json file
# translate json report to csv report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;We will start by writing the code to read a PDF, including deciding which fields we want in our report. This is the part you’ll need to tweak to match your payslip structure. Once that is figured out, we’ll iterate the whole payslips directory.&lt;/p&gt;

&lt;p&gt;In the third step, I chose to add an extra step between the PDF and the CSV — a JSON report. Once we see everything works, we will remove the use of that file.&lt;/p&gt;

&lt;p&gt;Lastly, we’ll translate that JSON data into a CSV file. That CSV can then be easily converted to Google Sheets (just click "open with") or to Excel (instructions can be found &lt;a href="https://www.howtogeek.com/770734/how-to-convert-a-csv-file-to-microsoft-excel/" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That’s an easy nice plan, but you know how it goes — challenges are discovered along the way… Can you guess where things might get complex?&lt;/p&gt;

&lt;p&gt;Before we start — an important note: KEEP YOUR PAYSLIPS PRIVATE! If you upload your project to GitHub — be sure not to share those personal details! You can use &lt;code&gt;.gitignore&lt;/code&gt; for this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/payslips_pdf
pdf_rows.txt
report.json
report.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Shall we begin?&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%2Fd95z8ey2y6enersrpb3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd95z8ey2y6enersrpb3p.png" alt="meme" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Read PDF File
&lt;/h3&gt;

&lt;p&gt;We’ll start by reading the PDF and printing all the rows. That way we will know what appears in each row. This only needs to be done once (while a report will probably be created once a month or once a year), and it’s not part of the report -so we will create that in a separate file.&lt;/p&gt;

&lt;p&gt;Start by creating a new Python file (I called mine &lt;code&gt;pdf_to_txt.py&lt;/code&gt;) and write a function that reads the pdf and prints the result into a .txt file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
This will work, but there are 3 changes to make it more user-friendly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Get the file path as a command line argument, so the user can run it without touching the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add some error-catching with instructions in case the user runs the command wrong. (I added a warning color but you don’t have to)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will read PDF files in the main script as well, so moving this function there is better.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Give it a go! Try running the command with the right and the wrong arguments :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you run &lt;code&gt;py .\pdf_to_text.py&lt;/code&gt; you will see a new file in the project directory called &lt;code&gt;pdf_rows.txt&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
(Don’t worry, this is not my data — it is based on the example payslip from the image below)
&lt;h3&gt;
  
  
  Process The PDF Data
&lt;/h3&gt;

&lt;p&gt;Now that we know the structure in which the PDF is read — we can fish the desired values. In my case — here is the information I was interested in:&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%2Frawkn7zz87pb3zjg5p55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frawkn7zz87pb3zjg5p55.png" alt="payslip" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice there is data within the table (categories that might vary every month) and data outside the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data outside the table:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay Period&lt;/strong&gt; — Can be found on row 19&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gross Pay&lt;/strong&gt; — This one was tricky to find a rule for because it appears &lt;em&gt;after&lt;/em&gt; the payments list and doesn’t have the title “Gross Pay”.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, &lt;strong&gt;payments&lt;/strong&gt; and &lt;strong&gt;deductions&lt;/strong&gt; can vary, and not every month is the same. Therefore, gross pay might appear in a different row in different months.&lt;/p&gt;

&lt;p&gt;I did notice it appears right after the employee name — so that is what I used. Start by adding it hard-coded, and later we will get it externally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nett Pay&lt;/strong&gt;: This one is easy — it appears in line 17.&lt;/p&gt;

&lt;p&gt;I gathered those out-of-table values into a function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
&lt;strong&gt;Data inside the table&lt;/strong&gt;

&lt;p&gt;&lt;strong&gt;Payment and Deduction Details&lt;/strong&gt;: This is the juicy part! We’ll start by cutting the rows array to save a few milliseconds in the for loop coming up. Then, I needed to differentiate between &lt;strong&gt;list items&lt;/strong&gt; and other rows.&lt;/p&gt;

&lt;p&gt;I’ve noticed that within the whole file, list items are the only ones that match this rule: Start with an alphabetic character &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; end with a numeric character &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; contain a space (the last condition is to filter out wrong rows in &lt;em&gt;my&lt;/em&gt; payslip, you might not need that).&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Now that we have the group of lines, let’s process them to save in the JSON object. At this point, we don’t mind if it’s a payment or a deduction.

&lt;p&gt;For example, we’ll look at the pension item:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PENSION     G   150.00   587.49
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;I don’t care about the balance (the number on the right), but I do care about the code (&lt;strong&gt;&lt;em&gt;G&lt;/em&gt;&lt;/strong&gt; means it’s deducted from the Gross pay — before taxes — and &lt;strong&gt;&lt;em&gt;N&lt;/em&gt;&lt;/strong&gt; means it is deducted from the Nett pay — after taxes). So ideally, we’ll have &lt;code&gt;json_obj["Pension (G)"]=150.00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We’ll use the spaces to split the line. It’s good there are duplicate spaces — that way we can differentiate between space splitting between a couple of words and space splitting between a couple of fields.&lt;/p&gt;

&lt;p&gt;The description:&lt;/p&gt;

&lt;p&gt;We will find the first double-space and split by it.&lt;/p&gt;

&lt;p&gt;The code:&lt;/p&gt;

&lt;p&gt;The amount of spaces is dependent on the length of the description, so we can’t know in advance how many are there — that’s why I’ll use &lt;code&gt;lstrip()&lt;/code&gt; as well. Now the rest of the line starts with a non-space character.&lt;/p&gt;

&lt;p&gt;Not all list items have a code, so we want to check if the line starts with a code or a digit. If it’s a code — I wrap it in &lt;code&gt;()&lt;/code&gt; (including a space before the opening parenthesis) , and attach it to the description string. and if not — add nothing.&lt;/p&gt;

&lt;p&gt;The amount:&lt;/p&gt;

&lt;p&gt;If there was code — we’ll have more spaces to strip. If not, our line &lt;strong&gt;might&lt;/strong&gt; contain two amounts: The monthly and the balance.&lt;/p&gt;

&lt;p&gt;There are 4 cases I’ve noticed:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SALARY      Y   1234.67
# AVC PRCTG   G   1234.00   2345.00
# RSU TAX WH  N   -1234.00  -2345.56
# RSU TAX WH  N             -2345.56
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After extracting the category and code, we are left with:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1234.67
# 1234.00  2345.00 (we want the amount on the left)
# -1234.00  -2345.56 (we want the amount on the left)
# -2345.56 (we want the amount on the left, which is none)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;To cover cases 2–3, we’ll find the index of the spaces separating the amounts and cut the tail. It also works for the first case, where there is no space (aka no tail).&lt;/p&gt;

&lt;p&gt;To cover case 4, I’m relying on the difference between two types of categories with a single amount in the row: The first one is like the salary — where we want to save the amount, and the second type is like the tax withholds — where we want to ignore it. The difference is that only deductions keep track of the annual balance in the table — so I am checking for &lt;code&gt;-&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All together, that’s how it looks like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Write to JSON File
&lt;/h3&gt;

&lt;p&gt;This is not a mandatory step — we can work with a JSON object without exporting the values. I prefer seeing what it looks like, at least for the coding stage.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;At this point, you can run &lt;code&gt;py ./main.py&lt;/code&gt; and see a new &lt;code&gt;report.json&lt;/code&gt; pop up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale to Multiple PDF Files
&lt;/h3&gt;

&lt;p&gt;The only reason this step is getting a dedicated section is because wrapping the &lt;code&gt;pdf_to_dict&lt;/code&gt; in a for loop reveals an unpleasant surprise. To demonstrate it, I created a function called iterate_over_pdfs():&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
This is happening because the list of files is sorted by alphabetic order, so &lt;code&gt;10&lt;/code&gt; appears before &lt;code&gt;2&lt;/code&gt;. Having the report entries in chronological order can be considered crucial, and not just a nice-to-have feature. Therefore, we need to fix it!

&lt;p&gt;Originally, I thought I’d have to rename the files (Payslip1.pdf -&amp;gt; Payslip01.pdf), but there is a better solution: Sort the list by length. That way, &lt;code&gt;10&lt;/code&gt; will appear after &lt;code&gt;2&lt;/code&gt;. Then we can also tackle the &lt;code&gt;b'&amp;lt;STRING&amp;gt;'&lt;/code&gt; structure by decoding. Lastly, after running the script on Mac, I discovered the directory includes hidden files – so we need to filter them out.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;After adjusting the main function to loop over the new list we created – the &lt;code&gt;report.json&lt;/code&gt; file will contain an array of payslips (give it a try!).&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the CSV Report
&lt;/h3&gt;

&lt;p&gt;Because the items in payments and deductions might vary from payslip to payslip, this section is more than just direct translation. CSV is a relational dataset, which means we need to know in advance all the categories in payments and deductions and keep the entry empty for a payslip where it doesn’t exist. JSON, on the other hand, is non-relational and each entry specifies its keys.&lt;/p&gt;

&lt;p&gt;With that in mind, the first step in our CSV report is to collect the categories. All the categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collect the categories:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, at first glance, you might think to use &lt;code&gt;Set&lt;/code&gt; for that — because we want all categories to appear only once. I’ve tried that. The problem with this is that sets are unlisted, and I find it important to match the order of items that appear in the original payslips. When using lists, don’t forget to check if the item exists in the list before appending it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Now that we have this figured out: Remember earlier when we said we don’t care about which item list is a payment and which is a deduction? Well, we do care now! We don’t &lt;em&gt;have to&lt;/em&gt; separate, but I’d expect a payslip report to have all payments on the right and all deductions on the left, not mixed.

&lt;p&gt;Although each payslip might have different list items — some will always exist (because you’ll always pay your taxes ;) ). We can use this to our benefit — and flag &lt;code&gt;PAYE&lt;/code&gt; as the start of the deductions! (I'm pretty sure PAYE is only in Ireland, so you'll need to change it to match your payslip)&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Finally, I return a single list, because there is no use in separating the payments from the deductions — the split was to assure payments will appear on the right and deductions will appear on the left.

&lt;p&gt;&lt;strong&gt;Populate the CSV table:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the categories, we can start populating the CSV table:&lt;/p&gt;

&lt;p&gt;Each payslip will be a row, and each row will have the fields in a specific order split by a comma. I find it easier to organize the fields in a list, and then join them. Fields that appear in the categories but not in the payslip — will remain empty:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Lastly, we will write to the CSV file:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
After this, you’ll have a nice CSV report with all your payslips!

&lt;p&gt;You can make it easier to read by downloading the VS extension &lt;a href="https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv" rel="noopener noreferrer"&gt;RainbowCSV&lt;/a&gt; (or any parallel of another IDE)&lt;/p&gt;
&lt;h3&gt;
  
  
  Remove the use of the .json file
&lt;/h3&gt;

&lt;p&gt;Once we know things work, we don’t need to write to the JSON file: Remove the section of &lt;code&gt;# save dict to json file&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add a success message
&lt;/h3&gt;

&lt;p&gt;It’s nice to notify the user that the script is done successfully. for that, we will need to expand &lt;code&gt;print_warning()&lt;/code&gt; to &lt;code&gt;print_in_color()&lt;/code&gt;, and add the option for green color:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Don’t forget to update the call in pdf_to_txt.py as well!&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the employee’s name as an argument
&lt;/h3&gt;

&lt;p&gt;Once you have your script ready — you’ll want to share it with your colleagues and friends! For a nice user experience, we’ll export the employee name to come from the command line, rather than ask them to open the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read argument&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll start with the happy path — assuming the user entered the employee name — and add code that uses it:&lt;/p&gt;

&lt;p&gt;In pdf_to_dict(), instead of hard coding &lt;code&gt;EMPLOYEE_NAME = "IFAT NEUMANN"&lt;/code&gt;, we'll read it from the arguments: &lt;code&gt;employee_name = sys.argv[1]&lt;/code&gt;. Don't forget to import sys!&lt;/p&gt;

&lt;p&gt;Now we’ll think of other scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No employee name was given&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What if the user did not enter any employee name? We’d wanna catch it as soon as possible, and notify them!&lt;/p&gt;

&lt;p&gt;Therefore, we’ll add a check in the first line of the main function. Now, the intuition is to initialize the &lt;code&gt;employee_name&lt;/code&gt; variable there - but this will cause bubbling function properties until it reaches the function that uses this variable - and I don't find it a very clean approach.&lt;/p&gt;

&lt;p&gt;Lastly, I’ll just try accessing this field — and catch if it’s not there:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that adding exceptions means the print_warning() function moves to main.py. Otherwise, you’ll get an error:
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ImportError: cannot import name 'print_warning' from partially initialized 
module 'pdf_to_text' (most likely due to a circular import)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Employee name without quotes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are asking the user to wrap the name with quotes because command line arguments are split by spaces. The only argument we expect is the employee name, so if there is another argument — we know they did not use quotes and we can notify them:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
You can skip the quotation requirements and loop over arguments, collecting all the parts of the user name — but I find this approach adds unnecessary complexity.

&lt;p&gt;&lt;strong&gt;The employee’s name doesn’t appear on the payslip&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We won’t be able to find gross pay if we don’t have the employee’s name as it appears on the payslip.&lt;/p&gt;

&lt;p&gt;The earlier spot to identify the mismatch is when we read the pdf. Therefore — well add the check at the beginning of the pdf_to_dict() function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that I moved the &lt;code&gt;employee_name&lt;/code&gt; variable to this function because it’s easier to read (rather than &lt;code&gt;if sys.argv[1] not in rows&lt;/code&gt;). After that, I pass it to get_fixed_values().

&lt;p&gt;Lastly, we catch the error back in the main function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
To wrap it all up — don’t forget to update your README file with the new instructions.
&lt;h3&gt;
  
  
  The full script
&lt;/h3&gt;

&lt;p&gt;Here is the full code of main.py:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And here is pdf_to_txt.py (note it’s calling functions from main.py):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Hope you enjoyed this one! Interested in another weekend coding project? Check out my &lt;a href="https://cupofcode.blog/code-email-sending/" rel="noopener noreferrer"&gt;automated email-sending blog post&lt;/a&gt;!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fiw809sodqfga2uycepbz.png" alt="footer" width="700" height="175"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cupofcode.blog/" rel="noopener noreferrer"&gt;https://cupofcode.blog/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :) Thank you for your support!&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>beginners</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Spotify API: How To Create a Data Set of Songs</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Mon, 18 Mar 2024 21:33:55 +0000</pubDate>
      <link>https://forem.com/cupofcode/spotify-api-how-to-create-a-data-set-of-songs-874</link>
      <guid>https://forem.com/cupofcode/spotify-api-how-to-create-a-data-set-of-songs-874</guid>
      <description>&lt;h2&gt;
  
  
  A Fun Tutorial using Python, JSON, and Spotify API!
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;You might find it more comfortable to read this in &lt;a href="https://cupofcode.blog/spotify-api/" rel="noopener noreferrer"&gt;my website&lt;/a&gt;.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you might know, I’ve recently written a Taylor Swift-themed Wordle game in React. To focus on the game logic, I downloaded a Taylor Swift data set from the Internet. Sounds good, right?&lt;/p&gt;

&lt;p&gt;I integrated this data set into the project and happily continued coding. Suddenly, I realized that the song I was currently listening to wasn’t on the list! This means that the list was too old (or that Tay-Tay is releasing new albums at a rapid rate). Right then and there I decided that I could trust no one — and should create my own data set!&lt;/p&gt;

&lt;p&gt;Not only that, but I saw here a good opportunity to get hands-on experience with Spotify API :D.&lt;/p&gt;

&lt;p&gt;So, what is on the agenda today?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;br&gt;
What data do I need for my Wordle game?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Theoretical&lt;/strong&gt;&lt;br&gt;
Because we came here to learn!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connecting to Spotify API + Performing API calls&lt;/strong&gt;&lt;br&gt;
And important things to notice!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Processing&lt;/strong&gt;&lt;br&gt;
With all due respect to &lt;em&gt;Taylor’s Version&lt;/em&gt;, of course.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modifications for future use&lt;/strong&gt;&lt;br&gt;
How to update the data set without running &lt;strong&gt;everything&lt;/strong&gt; again.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shall we begin?&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;I wrote a Wordle game themed with Taylor Swift song titles. To accommodate that, I am interested in a JSON file containing information about all Taylor Swift songs, with these restrictions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No duplicates&lt;/strong&gt;: This is a requirement that the queen made very challenging with her album re-recordings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Song titles no longer than 13 characters&lt;/strong&gt;: This is so I have a reasonable amount of characters to present in a word-guessing game. Six tries are probably not enough to guess “&lt;a href="https://www.youtube.com/watch?v=WA4iX5D9Z64" rel="noopener noreferrer"&gt;&lt;em&gt;We Are Never Ever Getting Back Together&lt;/em&gt;&lt;/a&gt;”, not to mention the width of a mobile view!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Every object needs to have a split array&lt;/strong&gt;: This array would indicate after which indexes there were spaces, in a space-less string of the song title. For example, “shakeitoff”-&amp;gt; [4,6] because:&lt;/p&gt;

&lt;p&gt;Shake it off&lt;br&gt;
01234 56 789&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every object needs to have an album_title&lt;/strong&gt; property, to let the user of the game know where they can find this song.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The list has to be &lt;strong&gt;shuffled&lt;/strong&gt; because we don’t want a guessing game such as Wordle to have a pattern in the solutions!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why JSON and not CSV?
&lt;/h3&gt;

&lt;p&gt;JSON is &lt;strong&gt;JavaScript Object Notation&lt;/strong&gt;, and CSV stands for &lt;strong&gt;Comma Separated Values&lt;/strong&gt;. Both are great ways to save data, and each has its advantages.&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%2Fr3jtpgy3qmaw1srvxso8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3jtpgy3qmaw1srvxso8.png" alt="JSON vs CSV" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above you can see dataset examples I made to show you the difference between the formats’ syntax. Note that CSV, as the name implies, is sensitive to commas, so you’ll need to wrap every comma-containing string in your data with quotation marks (the split array is a good example of that). On the other hand, &lt;em&gt;everything&lt;/em&gt; is wrapped in quotation marks in JSON files.&lt;/p&gt;

&lt;p&gt;In my case, the data is pretty simple (string, string, and a short array) and both formats will do the job. I chose JSON because I wanted this data set file to be reachable from outside the game. That way, when I update the file I don’t need to redeploy the game. JSON is the recommended way for communication between web applications and APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theoretical
&lt;/h2&gt;

&lt;p&gt;We’ll start with the new terms we touch on in this blog post.&lt;/p&gt;

&lt;p&gt;If you read the &lt;a href="https://developer.spotify.com/documentation/web-api/concepts/authorization" rel="noopener noreferrer"&gt;Spotify for developers documentation&lt;/a&gt;, you’ll see that &lt;em&gt;“Spotify implements the &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; authorization framework”&lt;/em&gt;. What is OAuth 2.0?&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth 2.0
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;OAuth 2.0, which stands for “Open Authorization”, is a standard designed to allow a website or application to access resources hosted by other web apps on behalf of a user. (&lt;a href="https://auth0.com/intro-to-iam/what-is-oauth-2" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A nice analogy for this is a coffee shop restroom. In some restaurants, there is a code on the bathroom door you can get only if you buy with the cashier. The code is printed on the receipt and is replaced daily. The restaurant does this to &lt;strong&gt;authorize&lt;/strong&gt; only the paying customers to use the restroom.&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%2Ffevv3r9uifkofagt4wcn.jpeg" 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%2Ffevv3r9uifkofagt4wcn.jpeg" alt="*Who knows where this coffee shop analogy came from*" width="324" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach is similar to OAuth 2.0. The customer has the access they need, but it’s the owner of the service who withdraws the access every day (and changes the code). If, for example, the access to the restroom would have been done with a key, it’s the key holder that has the access. With a key to control access, customers come to the cashier and ask for the key, use it, and then return the key. A customer with a key has access that can not be revoked (not as quickly and easily as a code).&lt;/p&gt;

&lt;p&gt;When looking at Spotify, as you will soon see, we won’t be getting any permanent password to access the data. Instead, we receive an &lt;strong&gt;access token&lt;/strong&gt; that gives us temporary access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Token
&lt;/h3&gt;

&lt;p&gt;In general, there are two types of information you can pull from Spotify: General data (artists, albums, songs) and user-specific data (liked songs, “for you” playlists, etc). We want the first category — which is what Spotify calls &lt;a href="https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow" rel="noopener noreferrer"&gt;Client credentials workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AtdA-b-qbq0c28RaVDKIq3Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AtdA-b-qbq0c28RaVDKIq3Q.png" alt="[Client credentials workflow](https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow)" width="783" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our application will be requesting the &lt;strong&gt;Spotify accounts service&lt;/strong&gt; for an access token. With that access token, we will make requests to &lt;strong&gt;Spotify Web API&lt;/strong&gt;. Note that there are two players here: &lt;strong&gt;Spotify accounts service&lt;/strong&gt; is the coffee shop cashier and &lt;strong&gt;Spotify Web API&lt;/strong&gt; is the restroom ;)&lt;/p&gt;

&lt;p&gt;The response from the &lt;strong&gt;Spotify accounts service&lt;/strong&gt; is returned in this format:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "access_token": "NgCXRKc...MzYjw",
   "token_type": "bearer",
   "expires_in": 3600
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This means that we can use &lt;em&gt;this&lt;/em&gt; token in &lt;em&gt;the next hour&lt;/em&gt;, with the &lt;strong&gt;bearer token type&lt;/strong&gt;. What is this bearer?&lt;/p&gt;
&lt;h3&gt;
  
  
  Bearer Authentication
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves &lt;a href="https://coinformant.com.au/what-is-a-security-token/" rel="noopener noreferrer"&gt;security tokens&lt;/a&gt; called bearer tokens.&lt;br&gt;
 The name “Bearer authentication” can be understood as “give access to the bearer of this token”. The client must send this token in the Authorization header when making requests to protected resources. (&lt;a href="https://www.devopsschool.com/blog/what-is-bearer-token-and-how-it-works/" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that when we build our request to Spotify API, we will need to add a header of authorization, using the word “Bearer” and attach the access token: {“Authorization”: “Bearer “ + access_token}.&lt;/p&gt;

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

&lt;p&gt;Let’s see it in the code!&lt;/p&gt;
&lt;h2&gt;
  
  
  Connecting to Spotify API
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Getting The Access Token
&lt;/h3&gt;

&lt;p&gt;You might’ve noticed in the &lt;a href="https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow" rel="noopener noreferrer"&gt;Client credentials workflow&lt;/a&gt; above that to get an access token, we need client_id and client_secret values. The client in this case is a Spotify &lt;strong&gt;project&lt;/strong&gt;. Therefore, the first thing you need is to create a Spotify project/application in &lt;a href="https://developer.spotify.com/" rel="noopener noreferrer"&gt;developer.spotify.com&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Log into your account. It is the same as your “regular” Spotify account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the dashboard and &lt;em&gt;Create a new project&lt;/em&gt;. This is the configuration I used:&lt;/p&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%2F7xy7my2m9y56w7ibpcon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xy7my2m9y56w7ibpcon.png" width="800" height="1208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click &lt;em&gt;Save&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;em&gt;Settings&lt;/em&gt;, You will find your Client ID and Client Secret. Grab those and open the VS code! (or another IDE of your choice)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Using the Access Token
&lt;/h3&gt;

&lt;p&gt;We will start by saving the client variables in the ENV.env file. I suggest you also create example.env and .gitignore files &lt;em&gt;now&lt;/em&gt; so you won’t accidentally upload your client secret to GitHub!&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%2F2kffa6m7mh6zmev42l33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2kffa6m7mh6zmev42l33.png" alt="Don’t let your secrets out!" width="451" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s go back to the IDE. We’ll start by importing the environment variables:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Now we need to create the request in a very specific structure. We’ll do it exactly as &lt;a href="https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; says:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Send a POST request to the /api/token endpoint.
- grant_type: Set it to client_credentials.
- Authorization: Basic &amp;lt;base64 encoded *client_id**:**client_secret*&amp;gt;,
- Content-Type: set to application/x-www-form-urlencoded.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On line 18 we are sending a Post request to the &lt;em&gt;/api/token&lt;/em&gt; &lt;strong&gt;endpoint&lt;/strong&gt;. We can see this &lt;strong&gt;endpoint&lt;/strong&gt; in the code on line 12.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We set grant_type to &lt;em&gt;client_credentials&lt;/em&gt; on line 17.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We create the header with authorization and content type on lines 13–16. To create  string, we first create the string &lt;em&gt;client_id&lt;/em&gt;&lt;em&gt;:&lt;/em&gt;&lt;em&gt;client_secret&lt;/em&gt; on line 10, then we encode it in base 64 on line 11.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, we decode the JSON data on line 19.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This looks good but it’s not yet working.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run this (with &lt;code&gt;py ./main.py&lt;/code&gt; in the terminal), you’ll get the following error:&lt;/p&gt;

&lt;p&gt;TypeError: a bytes-like object is required, not ‘str’. This error arises while trying to encode to base64 (base64.b64encode(client_id_and_secret)). This means we need to encode client_id_and_secret to be in bytes, not in string. The solution is to add a “translation“ to bytes between lines 10–11 above:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client_id_and_secret = client_id + ":" + client_secret
client_id_and_secret_bytes = client_id_and_secret.encode("utf-8")
client_id_and_secret_base64 = base64.b64encode(client_id_and_secret_bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After doing that, we reach the next error: TypeError: can only concatenate str (not “bytes”) to str”. When we try to compose the authorization string (“Basic “ + client_id_and_secret_base64). Now the problem is that we don’t have a string!&lt;br&gt;
There is an easy fix for that: Wrap our client_id_and_secret_base64 with str(...).&lt;/p&gt;

&lt;p&gt;Cool, now it runs! But hey, the result isn’t the JSON we expected: {‘error’: ‘invalid_client’}. Another bug! Looks like there is a problem with the client ID and secret we send. I test it by printing: print(“Basic “ + str(client_id_and_secret_base64)) and I see Basic b’MjY5N…’ . Wait, where did this b'..' came from?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bytes literals are always prefixed with ‘b’ or ‘B’; they produce an instance of the bytes type instead of the str type. (&lt;a href="https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means we are stringifying the object, but not accurately enough (‘A’ != b’A’). For the Authorization header, we want a UTF-8 string — so we need to specify that: str(...,"utf-8")&lt;/p&gt;

&lt;p&gt;Now it works! Note that we need to extract the access_token from the JSON (line 20 below):&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Performing API Calls
&lt;/h2&gt;

&lt;p&gt;Now that we have the access token, we can make API calls! But which APIs do we need?&lt;/p&gt;

&lt;p&gt;In my project, I want all of Taylor Swift’s songs. After looking at the &lt;a href="https://developer.spotify.com/documentation/web-api/reference" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, it looks like my script should do the following calls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.spotify.com/documentation/web-api/reference/search" rel="noopener noreferrer"&gt;Search&lt;/a&gt; for Tay-tay’s artist ID&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With that ID, &lt;a href="https://developer.spotify.com/documentation/web-api/reference/get-an-artists-albums" rel="noopener noreferrer"&gt;get artist’s albums&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each album, &lt;a href="https://developer.spotify.com/documentation/web-api/reference/get-an-albums-tracks" rel="noopener noreferrer"&gt;get the album’s tracks&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cool! I’m ready, are you ready?&lt;/p&gt;

&lt;h3&gt;
  
  
  GET Artist ID
&lt;/h3&gt;

&lt;p&gt;I played with the “try it” tool on the website and got this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3078%2F1%2Ar_8v62XytSWm34k9WbUIqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3078%2F1%2Ar_8v62XytSWm34k9WbUIqw.png" alt="[Search](https://developer.spotify.com/documentation/web-api/reference/search) in Spotify documentation" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s implement something similar in our code:&lt;/p&gt;

&lt;p&gt;Looks like we need two variables to create a GET request: URL and header. We saw those two in the POST request above (result = post(url, headers=headers, data=data))&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We’ll write the URL similar to the one in the Spotify tool: &lt;a href="https://api.spotify.com/v1/search?q=TaylorSwift&amp;amp;type=artist" rel="noopener noreferrer"&gt;https://api.spotify.com/v1/search?q=TaylorSwift&amp;amp;type=artist&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The header has &lt;strong&gt;bearer&lt;/strong&gt; authorization, using the access token from the previous function: {“Authorization”: “Bearer “ + token}&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, we need to pass the token as a parameter. I decided to pass the artist's name as well, to keep the function more generic: What if one day I’ll get sick of Taylor Swift? JOKING! I will never.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our function will look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Let’s look at the result we got:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Notice there are 13 Spotify artists with “Taylor Swift” in their name (&lt;code&gt;total&lt;/code&gt;: 13 on line 10), but there is only one Queen T! Therefore, limiting the result items to be of size 1 will get us the ID we need:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add &amp;amp;limit=1 to the end of the URL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;get to the id property by using json_result[“artists”][“items”][0][“id”].&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have the ID, we don’t need to perform this call anymore. Our main code will look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
# main
TAYLOR_SWIFT_ARTIST_ID = "06HL4z0CvFAxyc27GXpf02"

access_token = get_access_token('ENV.env')
artist_id = TAYLOR_SWIFT_ARTIST_ID # get_artist_id(access_token, "Taylor Swift")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  GET Artist Albums
&lt;/h3&gt;

&lt;p&gt;Now that we understand how an API call works, let’s look at the next one because it has a complexity we didn’t need to think about before: There is only one Taylor Swift, but there are multiple albums!&lt;/p&gt;

&lt;p&gt;We’ll start with a simple scenario:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
When you run this code, you’ll see the result you get is pretty big for the human eye and includes a lot of information we don’t need. As much as it is tempting to do processing here, let’s keep this function true to its name — getting the albums from Spotify. Therefore, we will just return return json_result[“items”].&lt;/p&gt;

&lt;p&gt;The next step is to create a dictionary of album names to album IDs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
The reason I’m grabbing both name and id is because the name is used in my data set, and the id is used later to grab the album tracks!

&lt;p&gt;Running this code shows that this small amount of processing resulted in a much smaller JSON:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Wait. This list is &lt;strong&gt;too small&lt;/strong&gt; — it does not contain all the albums! Any guess why is that? Let’s look at the fields of the response we got:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
We sent a short and sweet URL (&lt;a href="https://api.spotify.com/v1/artists/%7Bartist_id%7D/albums" rel="noopener noreferrer"&gt;https://api.spotify.com/v1/artists/{artist_id}/albums&lt;/a&gt;), and on lines 2–3 above it looks like the URL has some default fields that affect our response: &lt;br&gt;
?include_groups=album,single,compilation,appears_on&amp;amp;offset=0&amp;amp;limit=20

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;According to the documentation, we can filter the response to include only studio albums: .../albums?include_groups=album. That will drastically lower that 'total':387 on line 10!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;limit&lt;/strong&gt; is the maximum number of items to return (line 5). The default is 20, the minimum is 1 and the maximum is 50. Let’s do 50, shall we?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;offset&lt;/strong&gt; is the index of the first item to return (line 8). The default is 0, which means we start with the first item. We don’t need to touch this one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all that new information, our new URL will be: &lt;a href="https://api.spotify.com/v1/artists/%7Bartist_id%7D/albums?limit=50&amp;amp;include_groups=album" rel="noopener noreferrer"&gt;https://api.spotify.com/v1/artists/{artist_id}/albums?limit=50&amp;amp;include_groups=album&lt;/a&gt;, and the response coming back is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Therefore, the output of create_album_name_to_id_dict() is:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  GET Album Tracks
&lt;/h3&gt;

&lt;p&gt;Like with the albums, we’ll start with a simple call:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Given an album id, we pull the tracks. In a separate function we’ll create a song-to-album dictionary:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
We will loop over these functions for each album in the album_name_to_id dictionary and the result will be:
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Welcome To New York (Taylor's Version)": '1989',
    ...
    'Teardrops on My Guitar - Pop Version': 'Taylor Swift'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;(332 songs, the full list can be found &lt;a href="https://gist.github.com/cupofcodeblog/7cf87b687909de117a12c1345cd6fd23" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This wraps up the section of the API calls. Next, we will start processing the data, which might require us to come back for some adjustments.&lt;/p&gt;

&lt;p&gt;Before we do that, let’s make sure we are on the same page regarding main.py:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Data processing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Remove Special characters
&lt;/h3&gt;

&lt;p&gt;If you peek at &lt;a href="https://gist.github.com/cupofcodeblog/7cf87b687909de117a12c1345cd6fd23" rel="noopener noreferrer"&gt;the songs we gathered&lt;/a&gt;, you will see some song titles that are not Wordle-ready. Wordle solutions should not have special characters like commas, parentheses, single quotation marks, and digits.&lt;/p&gt;

&lt;p&gt;Because of that, we need to create a function that removes those characters. Hey, do you know what would be a fun way to do that? Test-driven development!&lt;/p&gt;

&lt;p&gt;We’ll start by exporting song_to_album into a file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    song_to_album.update(songs_to_one_album)
data_json_format = json.dumps(song_to_album)
file = open("song_to_album.json", "w")
file.writelines(data_json_format)
file.close()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then, we’ll create a name-processing function and run the names through that function. After that, we’ll loop over the list with another function checking if the names are alphabetic, and print the remaining “bad ones”.&lt;/p&gt;

&lt;p&gt;That way, we’ll adjust our name-processing function until all the names are valid!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On lines 4–6, we open the file and save the JSON object in a variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 9 we loop over the songs (the keys of the dictionary) and on line 10 we call the processing function. We start with this function empty.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 11 we check if the song name without spaces is alphabetic. If not, we print that name.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is our starting point:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wordlestruck-spotify-api&amp;gt; py -m src.test_process_name_func
Welcome To New York (Taylor's Version)
...
"Slut!" (Taylor's Version) (From The Vault)
...
Is It Over Now? (Taylor's Version) (From The Vault)
Anti-Hero
You're On Your Own, Kid
Question...?
...
the lakes - bonus track
Miss Americana &amp;amp; The Heartbreak Prince
Soon You’ll Get Better
ME!
...

---
 190 songs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Looks like we have 190 song titles to fix! Don’t worry, this amount will drop pretty quickly.&lt;/p&gt;

&lt;p&gt;At this point, you might wonder why we are “wasting time” on all song titles, instead of working on the ones that are no longer than 13 characters. This is because we have to “clean” the song titles to know which are in the correct length.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s start writing the function!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With all due respect to Taylor’s version, we’ll start by trimming the parenthesis. It doesn’t matter if it’s round or square — we don’t need it in our Wordle solution. I created a dedicated function for this part:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
We are down to 95 song titles to fix!&lt;/p&gt;

&lt;p&gt;The next thing that pops up in the output is the suffix after the “-“ character. Let’s add code for that:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_name(name):
  ...
  index_of_hyphen = name.find(" - ")
  if(index_of_hyphen &amp;gt; 0):
    name = name[:index_of_hyphen]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now we are down to 43 “bad” songs! At this point you might notice some duplicates — this is ok, we have a dedicated section for that later.&lt;/p&gt;

&lt;p&gt;The next step is to start using the replace function! The first replacement will be of the single quote character, on all its varies:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name = name.replace("'", "").replace("’", "").replace("‘", "")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This brought us down to 24 “bad” songs!&lt;/p&gt;

&lt;p&gt;The next character to remove is “…” but note that we can’t just remove it because it might function as a separation between words (&lt;em&gt;Come Back…Be Here&lt;/em&gt;). So, we replace it with white space. This means the other songs in this group will have a trailing whitespace, which we will resolve with .strip().&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_name(name):
  ...
  name = name.replace("...", " ")
  return name.strip()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Now I’ll add the replacement for the characters ?/!/,/. which will resolve for us another 9 songs! NOTE that this should be added after the ... trimming.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, we will replace 1 with one, 22 with twenty two and &amp;amp; with and.&lt;/p&gt;

&lt;p&gt;wordlestruck-spotify-api&amp;gt; py -m src.test_process_name_func&lt;br&gt;
"Slut"&lt;br&gt;
Anti-Hero&lt;br&gt;
Back To December/Apologize/Youre Not Sorry&lt;/p&gt;



&lt;p&gt;3 songs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last thing to do is remove the double quotes and the -. Note the difference between - in a song name like Anti-Hero and the one trailing a song name (the lakes — bonus track). The trailing one is padded by whitespaces, and we do it before!&lt;/p&gt;

&lt;p&gt;I’m ignoring the song title Back To December/Apologize/Youre Not Sorry because it is definitely longer than 13 characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s it! This is our final function:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
At this point, we have a beautiful name-processing function, but we haven’t called it yet!&lt;/p&gt;

&lt;p&gt;We’ll go to where we create the song_to_album dictionary, and add 2 things: First, process the name. Then, if the name is no more than 13 characters — add it to the dictionary!&lt;/p&gt;

&lt;p&gt;Make sure you use the processed name as the key!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
The new song_to_album list can be found &lt;a href="https://gist.github.com/cupofcodeblog/87acf206107b44746397b012118693fd" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Blank Space": "1989",
  ...
  "New Romantics": "1989 (Deluxe)",
  "Slut": "1989 (Taylor's Version)",
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;I like that there is a difference between the original album, the deluxe version, and the new songs from the vault. If you don’t like it, you are now fully capable of trimming the album titles :)&lt;/p&gt;

&lt;p&gt;It’s important to mention that the list isn’t final! But it’s good enough for this stage of the project. After we finish coding the simple version, we’ll come back and fine-tune the code. Don’t worry, you’ll leave this blog post with a full end-to-end project!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Split Array
&lt;/h2&gt;

&lt;p&gt;For the wordle game, each song should have 4 fields: id, song_title, album_title, and split array. We already have the song title and album title, so the next step is adding the split!&lt;/p&gt;

&lt;p&gt;Let’s start with a reminder of what we are trying to achieve:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The split array would indicate after which indexes there were spaces, in a space-less string of the song title. For example, &lt;em&gt;“shakeitoff”-&amp;gt; [4,6]&lt;/em&gt; because:&lt;br&gt;
&lt;em&gt;Shake it off&lt;/em&gt;&lt;br&gt;
&lt;em&gt;01234 56 789&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To do that, we will find the first occurrence of space, add it to the split array, remove it, and find the next occurrence, until there are no more spaces.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that I’m removing the space that I “checked in” so that we won’t stop on it in the next find. Also, you need to specify you want to replace only 1 occurrence: song_name.replace(" ","",1)

&lt;p&gt;Now, we need to create objects containing the properties we have so far:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
I chose to stringify the split array because it would be easier to read later.

&lt;p&gt;Lastly, we call the function in the main code:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
arr_with_split = create_arr_with_split(song_to_album)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Creating The Data Set
&lt;/h3&gt;

&lt;p&gt;For this to be a legit data set for the Wordle game, we are missing three actions: Shuffle the list, add ids, and save to a file&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We use an additional variable because shuffle() is a function that affects the input object. The replace() function, for example, did not. This is why we needed to save the modified string: str = str.replace(...)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The use of the function list(..) is to make the copy deep. Otherwise, shuffled would’ve pointed at the same array, and using shuffle(...) would’ve mixed our original list. It won’t be a problem now, but for future functionality, we need to keep the original list as it is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the for loop we use enumerate(..) to get access to the loop index (for i..).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fun fact: the lists are independent (arr_with_split is not shuffled and shuffled is shuffled), but the objects they contain are shared — the new id property we added for shuffled appear in arr_with_split objects as well!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last thing left to do is write our data set to a JSON file. I created functions for writing and reading from files. It’s very useful for testing!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
That’s it! This is our main.py:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
This was intense! We deserve a break!

&lt;p&gt;While waiting for my API calls to populate my dictionaries, I thought it would be cool to create a progress bar. I always wondered how the line gets rewritten in the terminal — so this was a good opportunity!&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%2F8lj7qw2nd7e4kqcgyoml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lj7qw2nd7e4kqcgyoml.png" alt="Let’s create a progress bar!" width="418" height="672"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Feature: Progress Bar
&lt;/h2&gt;

&lt;p&gt;Here is what it would look like:&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%2F6fmdll5ymv3re5grdlxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fmdll5ymv3re5grdlxh.png" alt="Tell me this is not cool!" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll start with the printing progress function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On lines 2–3, I define the characters I’ll use to represent full and empty parts of the bar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 4 is building the bar: The length of the bar will always be len_range, and the amount of █ and - are determined by i.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;printEnd needs to be \r for the line to override itself in updates, but I added the option \n to wrap it up (when i==len_range). This is because otherwise the next line I print after gets swallowed by the progress bar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, the print. I started with /r but that’s a personal preference. The main thing to notice is the structure of the string: 'Progress: |%s| %s/%s albums complete' % (bar, i, len_range)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that if your terminal window is not wide enough to contain the print in one line — the override won’t work!&lt;/p&gt;

&lt;p&gt;Now let’s call it in main:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that in line 3 we start the progress bar empty, and inside the for loop (line 9) we call the function with i+1, because the album on index 2 is the 3rd album.

&lt;p&gt;Also, to reach i in the for loop, we iterate over &lt;strong&gt;enumerate(&lt;/strong&gt;album_name_to_id*&lt;em&gt;)&lt;/em&gt;*.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht5a7oiz9rpdzj83hwi0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht5a7oiz9rpdzj83hwi0.gif" width="600" height="206"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Modifications For Future Use
&lt;/h2&gt;

&lt;p&gt;Tay Tay announced she is releasing a new Album on April 19th, and we need to prepare our code for that!&lt;/p&gt;

&lt;p&gt;After many trials and errors, I’ve decided to create a new data set on every update. This does not mean I will make all the API calls to Spotify and string processing on every update! You can, it will lead to the same result, but it’s not optimized.&lt;/p&gt;

&lt;p&gt;For the updating feature, I will be creating 4 files in the code:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1_album_to_name_id.json
2_song_to_album.json
3_arr_with_split.json
4_dataset.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After every pull, we save the relevant information, ensuring we execute only the necessary parts. Let’s see it in the code!&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding External Files
&lt;/h3&gt;

&lt;p&gt;The easy part is to add the write functions:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that the file path should be relative to the terminal’s directory! My terminal is open on the project’s main folder so the writing file paths start with src/ !

&lt;p&gt;Soon we will be adding read functionality for those files. It is too easy to update the &lt;strong&gt;write&lt;/strong&gt; file path and forget the &lt;strong&gt;read&lt;/strong&gt; path. Therefore, we will save those as variables:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Now we need to add read-from-files and ensure it works with empty files. What exactly do we want? It’s easier to figure out when we focus on a small group of albums, so we’ll look at the 1989 albums.
&lt;h3&gt;
  
  
  The 1989 Test
&lt;/h3&gt;

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

&lt;p&gt;If you look at the list of albums, you’ll see 1989 (Taylor’s version) is the most recent album. I considered the chronological order because I thought of making the actual API call. Then I realized I could just mock it. Anyway, 1989 it is!&lt;/p&gt;

&lt;p&gt;We’ll start by modifying album_to_name_id to contain only the original 1989 albums:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# album_name_to_id = create_album_name_to_id_dict(json_result_album_items)
album_name_to_id = {
  "1989 (Deluxe)": "1yGbNOtRIgdIiGHOEBaZWf",
  "1989": "5fy0X0JmZRZnVa2UEicIOl"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now we’ll execute the program and let it populate our files.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
So far so good. Now, let’s mock an update call:
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# album_name_to_id = create_album_name_to_id_dict(json_result_album_items)
album_name_to_id = {
  "1989 (Taylor's Version) [Deluxe]": "1o59UpKw81iHR0HPiSkJR0",
  "1989 (Taylor's Version)": "64LU4c1nfjz1t4VnGhagcg",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;And think about what we want the program to do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1_album_name_to_id.json should contain a total of 4 albums. We want to make sure we process only the new albums.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2_song_to_album.json should contain an additional 3 songs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3_arr_with_split.json should also contain an additional 3 songs, but we want to make sure we process only those!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;4_dataset.json will be overridden so no extra action is required from us.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Updating 1_album_name_to_id.json
&lt;/h3&gt;

&lt;p&gt;With the code as it is now, the new album dictionary will override the previous one:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
json_result_album_items = get_albums(access_token, artist_id, "album")
album_name_to_id = create_album_name_to_id_dict(json_result_album_items)

write_to_json_file(ALBUM_NAME_TO_ID_FILE_PATH, album_name_to_id)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;So let’s execute the code as usual, but read the previous dictionary and update the variable before writing to the file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On line 7 we merge the two album_name_to_id dictionaries. Note that we updated the new album with the old album so that the list will be LIFO = last in first out. This is critical for assigning the original album to each song.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 9 we update the file. Note that this line was originally before the for loop on line 3 but now it is after!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On line 6 we save the previous object of albums. This requires modifying the read function to accommodate the first execution of the program (when the file doesn’t exist):&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Note that with these changes, we only execute the songs section on the new albums!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Updating 2_song_to_album.json
&lt;/h3&gt;

&lt;p&gt;Look at the code as it is now and consider that we want to keep the original album names (and have "Blank Space": "1989" instead of "Blank Space": "1989 (Taylor’s Version)"):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;song_to_album = {}
for album_name in album_name_to_id:
  ...
write_to_json_file(SONG_TO_ALBUM_FILE_PATH, song_to_album)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;There are 2 ways to achieve this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let the old dictionary override the new dictionary values:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Extract the new songs. This means the second dictionary will only have new keys, and add those to the new list:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

We’ll go with option number 2 because it will be useful in the next section!&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Updating 3_arr_with_split.json
&lt;/h3&gt;

&lt;p&gt;As a reminder, this is the code as it is now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
arr_with_split = create_arr_with_split(song_to_album)

write_to_json_file(ARR_WITH_SPLIT_FILE_PATH, arr_with_split)
shuffled = list(arr_with_split)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;In this case, the logic will be similar. Just note that this is an array we are updating, so we use the .extend() function instead of .update().&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
I think it can be confusing to call the updated list “prev”, so I created a new variable:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;updated_arr_with_split = list(prev_arr_with_split) # new var for readablity
write_to_json_file(ARR_WITH_SPLIT_FILE_PATH, updated_arr_with_split)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;For this to work, we need to modify the read function to be able to return an empty array! The function as it is now returns only an empty dictionary if the file doesn’t exist. This can be achieved by a small tweak:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def from_file_to_json_obj(file_path, array = False):
    if Path(file_path).exists():
        ...
    return [] if array else {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This means that when we read from the array file, we add the array flag:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prev_arr_with_split = from_file_to_json_obj(ARR_WITH_SPLIT_FILE_PATH, array=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Now we’ll see it in action!
&lt;/h3&gt;

&lt;p&gt;Here is the updated main file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
There are 3 tests to perform:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;album_name_to_id = { &lt;br&gt;
"1989 (Deluxe)": "1yGbNOtRIgdIiGHOEBaZWf",&lt;br&gt;
"1989": "5fy0X0JmZRZnVa2UEicIOl"&lt;br&gt;
}&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;album_name_to_id = { &lt;br&gt;
"1989 (Taylor’s Version) [Deluxe]": "1o59UpKw81iHR0HPiSkJR0",&lt;br&gt;
"1989 (Taylor’s Version)": “64LU4c1nfjz1t4VnGhagcg"&lt;br&gt;
}&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;album_name_to_id = {&lt;br&gt;
"1989 (Taylor’s Version) [Deluxe]": "1o59UpKw81iHR0HPiSkJR0",&lt;br&gt;
"1989 (Taylor’s Version)": “64LU4c1nfjz1t4VnGhagcg",&lt;br&gt;
"1989 (Deluxe)": "1yGbNOtRIgdIiGHOEBaZWf",&lt;br&gt;
"1989": "5fy0X0JmZRZnVa2UEicIOl"&lt;br&gt;
}&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We want to start with an empty files directory and run test #1. Then, with those new files, run test #2 and see if the files look as we expected. After that, we want to move those files aside and run test #3 on a clean slate. Lastly, compare those files: This is our &lt;strong&gt;&lt;em&gt;one pull vs update&lt;/em&gt;&lt;/strong&gt; test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Oops
&lt;/h3&gt;

&lt;p&gt;The same thing happened to me again! I was working on this project, listening to Tay Tay (as one does), and I suddenly realized “&lt;em&gt;You’re losing me&lt;/em&gt;”, which I’ve been listening to on repeat 1, is not in the data set!&lt;/p&gt;

&lt;p&gt;When I started working on this project, I decided to include only the studio albums because the singles list was full of duplicates (Lavander Haze, Anti-Hero, …). Turns out I was too quick to dismiss that album type. Back to the API call!&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%2Fz4x5qxuusdd6u2ipktcq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4x5qxuusdd6u2ipktcq.png" alt="Taylor Swift’s Singles and EPs on Spotify" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulling the single songs
&lt;/h2&gt;

&lt;p&gt;There are two ways to gather the single albums along with the studio albums. One way is to make a single API call with include_groups=album,single. The other way is to make two separate API calls, one for include_groups=album and one for include_groups=single.&lt;/p&gt;

&lt;p&gt;I would’ve chosen the first option if the response would’ve brought the albums in chronological order. As it is now, the response contains the studio albums in chronological order, and then the singles in chronological order. I’ve tried sorting them by date but things got messy quickly.&lt;/p&gt;

&lt;p&gt;Therefore, I’ll be making two API calls. I will treat the singles API call as an update to the dataset. That way, all the duplicates won’t mess up the song dictionary, and the songs that are only singles will be added.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nice to meet you, Pagination
&lt;/h3&gt;

&lt;p&gt;If you make the API call with include_groups=single, you will see there is a total of 64 albums. This is above the maximum limit of items in a response — which means we need to paginate!&lt;/p&gt;

&lt;p&gt;I didn’t address pagination prior, because we used the API call with the include_groups=albums parameter and the total was 26. This is &lt;strong&gt;half&lt;/strong&gt; of the maximum amount of items coming back in the call (limit=50). Therefore I made the executive decision not to paginate on those.&lt;/p&gt;

&lt;p&gt;So, let’s talk about pagination!&lt;/p&gt;

&lt;p&gt;If you look at the response JSONs I shared earlier (for the call &lt;a href="https://api.spotify.com/v1/artists/06HL4z0CvFAxyc27GXpf02/albums" rel="noopener noreferrer"&gt;https://api.spotify.com/v1/artists/06HL4z0CvFAxyc27GXpf02/albums&lt;/a&gt; for example), you will see that one of the values in the response is next. This contains the URL to the next page of items (or None if there isn‘t one).&lt;/p&gt;

&lt;p&gt;This URL means that not all the data we asked for is coming in one response, and to get the next page of the data, we need to make another API call. The difference between those calls is the offset, which is the first item index appearing in the response.&lt;/p&gt;

&lt;p&gt;To make it clear, we’ll change our limit to be 5. With a total of 26 albums, we will need a total of 6 API calls to gather all the albums!&lt;/p&gt;

&lt;p&gt;Now, we want at least one call to get albums, and then keep calling until next==None. This is a perfect candidate for a do-while loop, which unfortunately does not exist in Python. I saw online you can create a version of it with While True:...break but it itches me to use While True. And then I had an epiphany: You know what will be fun? RECURSION :D Have you done those since your last job interview?&lt;/p&gt;

&lt;p&gt;The core of the recursion is to make an API call to the given URL, gather album names, and return that list after we concat that to the list returning from the recursive call.&lt;/p&gt;

&lt;p&gt;Our stopping condition will be if url == None.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s see it in the code!&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Lastly, the rest of our program doesn’t need to know what is happening under the hood, so we will wrap the call to the function and gather the parameters it needs:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that I’m adding a variable for album type, to reuse this function for the two API calls!

&lt;p&gt;This is a good place to add limit as an optional parameter because when we update our dataset we don’t need to pull all the albums, we can take a quick look in Spotify and eyeball estimate the amount.&lt;/p&gt;

&lt;p&gt;If we didn’t paginate, &lt;em&gt;limit&lt;/em&gt; would’ve worked by itself: One function call — one API call — one response. With recursion, limit is just telling how thin we want the slices. Therefore, we need to add a flag to indicate to our function that we don’t want the other pages!&lt;/p&gt;

&lt;p&gt;This is how I implemented this feature:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On line 9, I added to the function 2 parameters: limit and an optional offset. The offset is for testing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 14 I created a boolean that is true if the limit is smaller than 50 (which is &lt;strong&gt;my program’s default&lt;/strong&gt; limit).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the recursion, on line 4 I check if this is an update call. If it is: I return the items and exit. if not, we continue to the next iteration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lastly, we integrate this in the main code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I added an optional argument for the limit, which I checked for on line 1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If I want to pull the latest 5 albums, I run py -m src.main 5.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If I need an initial pull, I don’t add anything, and the default is set to 50.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 5 I pass that variable to get_albums().&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test your code:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an offset to get_albums(..., limit, offset=5), run py -m src.main, remove the offset get_albums(..., limit) and then run py -m src.main 5 - This is a dataset update!&lt;/p&gt;

&lt;p&gt;Compare the lists coming back from the update to the lists returning from a single pull. If you are pleased with the result — we can move on!&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%2Fwpxvcy1tw4b3izjdcvml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpxvcy1tw4b3izjdcvml.png" width="487" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Merging Singles in The Code
&lt;/h3&gt;

&lt;p&gt;As I mentioned, we will treat this list like an update, and only add the unique keys.&lt;/p&gt;

&lt;p&gt;A good way to achieve that is by looping over the album type:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On line 3 we add a for loop. The loop block contains all the code until the shuffle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now that we have two album_name_to_id dictionaries. Therefore, we insert the album_name_to_id_file_path variable inside the loop (line 4).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Skip Albums
&lt;/h2&gt;

&lt;p&gt;We have just a tiny issue to solve before closing this project. As a Swifty, when I look at the songs dictionary I see some strange pairings:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Better Man": "reputation Stadium Tour Surprise Song Playlist",
  "Love Story": "Live From Clear Channel Stripped 2008",
  "Santa Baby": "The Taylor Swift Holiday Collection",
  "September": "Spotify Singles",
  "I Want You Back": "Speak Now World Tour Live",
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Better Man should be linked to Red (Taylor’s Version), Love Story is from the Fearless album, and the rest are covers, which might sound nice but are useless in a word game.&lt;/p&gt;

&lt;p&gt;These oopsies don’t mean our code is wrong, but some albums will inevitably need to be skipped. In this list belong all the albums that should be ignored completely.&lt;/p&gt;

&lt;p&gt;My list is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
How do we add this feature to the code?&lt;/p&gt;

&lt;p&gt;We start by reading the file and saving it into a variable. Then, instead of looping over &lt;em&gt;all&lt;/em&gt; the albums received from the API call, we filter out the albums that appear in the skip dictionary. Let’s see it in the code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that we still save the full albums dictionary in our files, to keep track of the albums we’ve processed.

&lt;p&gt;After this change, the song dictionary is correct:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{&lt;br&gt;
  "Better Man": "Red (Taylor's Version)",&lt;br&gt;
  "Love Story": "Fearless (International Version)",&lt;br&gt;
  &lt;del&gt;"Santa Baby": "The Taylor Swift Holiday Collection",&lt;/del&gt;&lt;br&gt;
  &lt;del&gt;"September": "Spotify Singles",&lt;/del&gt;&lt;br&gt;
  &lt;del&gt;"I Want You Back": "Speak Now World Tour Live",&lt;/del&gt;&lt;br&gt;
  ...&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Well, almost.&lt;br&gt;
&lt;/h3&gt;

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

&lt;p&gt;Tay Tay decided to rerecord &lt;em&gt;SuperStar&lt;/em&gt; but not keep the original name — which for our dictionary means “new song!”. I can solve it by unifying the dictionary keys — all upper-case or all lower-case.&lt;/p&gt;

&lt;p&gt;I decided not to do so because I wanted to preserve the original song titles. Therefore, my game will have 2 occurrences of the song Superstar. I can’t recall the last 5 solution words in Wordle, so hopefully my users won’t notice either :D&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now we are done!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What a journey! For a second there, I thought this blog post would never end!&lt;/p&gt;

&lt;p&gt;Today we learned about OAuth2.0, bearer authentication, and Spotify API. We created API calls, processed data, and created a beautiful clean dataset.&lt;/p&gt;

&lt;p&gt;This was the fourth and last part of my &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/" rel="noopener noreferrer"&gt;Wordle series&lt;/a&gt;. See you in the next one!&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :) Thank you for your support!&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>React Tutorial: Create Your Twist on Wordle</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sat, 24 Feb 2024 09:01:33 +0000</pubDate>
      <link>https://forem.com/cupofcode/react-tutorial-create-your-twist-on-wordle-3ip9</link>
      <guid>https://forem.com/cupofcode/react-tutorial-create-your-twist-on-wordle-3ip9</guid>
      <description>&lt;h2&gt;
  
  
  There are many React tutorials to create Wordle clones… but why stop there? This is how I created a Taylor Swift-themed Wordle!
&lt;/h2&gt;

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

&lt;p&gt;I’ve recently decided I would like to improve my React skills, and I knew the way to do it was to create a fun project. Therefore, I found a Wordle tutorial! It’s called &lt;a href="https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX" rel="noopener noreferrer"&gt;&lt;strong&gt;Make a Wordle Clone with React&lt;/strong&gt; by Net Ninja&lt;/a&gt;. That was a great start.&lt;/p&gt;

&lt;p&gt;With this basic version of Wordle implementation (which from now on I will refer to as &lt;em&gt;“the base code”&lt;/em&gt;), I chose to invest my best efforts in creating a game I would like to see in the world — &lt;em&gt;“Guess the Taylor Swift Song Title”&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;In addition, I developed the base code to include all the cool features the original Wordle game has to offer! You can read about it in &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/" rel="noopener noreferrer"&gt;my previous blog posts&lt;/a&gt;. &lt;strong&gt;To clarify, this blog post does not depend on them&lt;/strong&gt;. Fun fact, the first thing I did after finishing the base code tutorial was adjust it to longer solution words.&lt;/p&gt;

&lt;p&gt;So, grab the code base from &lt;em&gt;Net Ninja&lt;/em&gt;’s GitHub (&lt;a href="https://github.com/iamshaunjp/React-Wordle/tree/lesson-16" rel="noopener noreferrer"&gt;link&lt;/a&gt;), and join me in this tutorial! Instructions on how to clone the GitHub project can be found &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/#pullBaseCode" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Which theme are you planning to give your Wordle? Let me know in the comments!&lt;/p&gt;

&lt;p&gt;The agenda for this blog post is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Overview of the base code components&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust the game to words longer than 5 letters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust the game to multi-word solutions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bonus — use an external data set&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Are you ready for it? ;)&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%2Fo6hq2bonmecnpgbclona.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6hq2bonmecnpgbclona.gif" alt="I’ll tame myself with the Taylor Swift references, I promise" width="400" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Overview of The Base Code
&lt;/h1&gt;

&lt;p&gt;No need to show here how to do those steps because &lt;a href="https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX" rel="noopener noreferrer"&gt;the YouTube tutorial&lt;/a&gt; does it very well. I’ll detail the state of the project post the tutorial, to connect you to the rest of the blog post.&lt;/p&gt;

&lt;p&gt;The base code from &lt;em&gt;Net Ninja&lt;/em&gt;’s GitHub can be found &lt;a href="https://github.com/iamshaunjp/React-Wordle/tree/lesson-16" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You can either clone his code and come join me now (instructions can be found &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/#pullBaseCode" rel="noopener noreferrer"&gt;here&lt;/a&gt;), or revisit this blog post after following his tutorial!&lt;/p&gt;

&lt;p&gt;Let’s take a look at the files:&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%2Fuowzn86ew7hikx1j3ovs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuowzn86ew7hikx1j3ovs.png" alt="Project structure" width="277" height="822"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;db.json&lt;/strong&gt; contains 2 JSON objects: &lt;code&gt;letters&lt;/code&gt; and &lt;code&gt;solutions&lt;/code&gt;. The letters are used for the keypad functionality and the solutions are currently 5 letter words, with an ID attached:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
      "letters": [
        {"key": "a"},
        {"key": "b"},
        ...
      ],
      "solutions": [
        {"id": 1, "word": "apple"},
        {"id": 2, "word": "hello"},
        ...
      ]
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The base code pulls the solution words from the JSON file by running &lt;code&gt;json-server&lt;/code&gt; in a separate terminal, which simulates a RESTful API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;node_modules&lt;/strong&gt; is where all your dependencies sit. If you don’t have this folder yet, you’ll have it after the next section (“Running the code”). We won’t be touching this directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;public&lt;/strong&gt; folder contains static files such as &lt;code&gt;index.html&lt;/code&gt;, images, and other assets. This is where we’ll set our title, favicon, and themed images!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;src&lt;/strong&gt; folder is divided into two sub-folders: Components and hooks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;components&lt;/strong&gt; directory has the different building blocks of the code:&lt;/p&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%2F2npiuvsu4rpki1oyumq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2npiuvsu4rpki1oyumq6.png" alt="Components" width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;hooks&lt;/strong&gt; directory has our only &lt;a href="https://www.w3schools.com/react/react_customhooks.asp" rel="noopener noreferrer"&gt;&lt;em&gt;custom hook&lt;/em&gt;&lt;/a&gt;: &lt;code&gt;useWordle.js&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;App.js&lt;/strong&gt; is the starting point of our game. This is where we fetch the solutions JSON.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;index.css&lt;/strong&gt; is where we make our game pretty :D&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt; is used to &lt;em&gt;run&lt;/em&gt; the project. This is also where you &lt;em&gt;build&lt;/em&gt; it if you want to &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/#deploying" rel="noopener noreferrer"&gt;upload your game&lt;/a&gt; to the World Wide Web ;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;README.md&lt;/strong&gt; is where you explain your project, provide instructions on how to run it, and give a shout-out to &lt;em&gt;Net Ninja&lt;/em&gt; thanking him for his base code :)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are more files in the project, but we won’t touch them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Running the code:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;You’ll need to install all the dependencies by running these 2 commands (if you haven’t already):
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i
npm i json-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is done only once in the project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Run the json-server:&lt;br&gt;
i. Open a new terminal&lt;br&gt;
ii. Run the command &lt;code&gt;json-server .\data\db.json --port 3001&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the project: Open &lt;code&gt;package.json&lt;/code&gt;, hover above "start" and click "Run Script".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2F4rmt35cejt3r174oz0f8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4rmt35cejt3r174oz0f8.png" alt="Screenshots from [Net Ninja’s YouTube tutorial](https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX)" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run the code you will see the page with the grid and the keypad. The keypad is not clickable and is here to show you all the letters and their colors. Above the grid there is the solution for this round, and the current guess the user is typing (for testing purposes).&lt;/p&gt;

&lt;p&gt;Every time the user types a guess and presses enter, the tiles flip and color with green, yellow, or grey. The game ends when the user guesses the solution word or when the user does not manage to guess the solution word within 6 tries. Then, a modal pops up with a winning/losing message, the solution word, and the number of guesses it took.&lt;/p&gt;

&lt;p&gt;Please note that every time you refresh the page, there will be a different solution word.&lt;/p&gt;

&lt;p&gt;The overview is done… Let’s start coding! :D&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%2Fzq6j87gmsh0py8ldw9wu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq6j87gmsh0py8ldw9wu.gif" alt="typing-gif" width="518" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Adjust to Words Longer Than 5 Letters
&lt;/h1&gt;

&lt;p&gt;The game as it is now is hardcoded for 5-letter words, in all parts of the application. Start by modifying your dataset to contain 6-letter words (so “apple” becomes “apples”, etc). Note that changing the data set will require re-running the &lt;code&gt;json-server&lt;/code&gt;: &lt;strong&gt;&lt;em&gt;ctrl+c&lt;/em&gt;&lt;/strong&gt; if you have it currently running, and then &lt;code&gt;json-server .\data\db.json --port 3001&lt;/code&gt;. Otherwise, you won’t see your changes.&lt;/p&gt;

&lt;p&gt;The next step is to swap the hard-coded &lt;code&gt;5&lt;/code&gt; with a &lt;code&gt;length&lt;/code&gt; variable. This information, at the moment, is easy to gather with &lt;code&gt;solution.length&lt;/code&gt; , but this won’t be the case when we add functionality for multi-word solutions later on ;)&lt;/p&gt;

&lt;p&gt;We shouldn’t just use “replace all” in the editor because not all hard-coded appearances are with the character &lt;code&gt;5&lt;/code&gt;, as you will soon see. Instead, we should think logically — which components need to know the word size? The first one that comes to mind is the row — this component is the one dictating how many tiles are in a single row in the grid. It’s not the only place to change, but it’s a good start!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Row Component
&lt;/h2&gt;

&lt;p&gt;We’ll start by observing the existing logic. For us to do that, I attached here all the places that reference the five letters:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  The current row (lines 6–13 above)
&lt;/h3&gt;

&lt;p&gt;This div creates the row that the user is typing into. It means the map iterates over the characters that are currently in the guess and completes the rest of the row with empty tiles.&lt;/p&gt;

&lt;p&gt;What does the code iterate on to finalize the empty tiles? The iteration is done on the array &lt;code&gt;[…Array(5-letters.length)]&lt;/code&gt; (line 10). This structure, &lt;code&gt;[…Array(5)]&lt;/code&gt;, is equivalent to &lt;code&gt;[0,1,2,3,4]&lt;/code&gt;. Considering we now have words longer than 5 letters, this value has to be modified:&lt;/p&gt;

&lt;p&gt;For now, we will add length as a parameter of the Row component (line 1 below), and later on deal with passing this value. On line 5 we are now mapping the array &lt;code&gt;[...Array(length-letters.length)]&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  The empty row (lines 17–23)
&lt;/h3&gt;

&lt;p&gt;This code returns the empty rows that appear after the current played row. The five divs are the five tiles in each row. We can’t return 5 hard-coded divs anymore, we need the number of divs to depend on the length of the word! We do that by using a map:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the new version, the divs have a key parameter (line 5 above). This is because it’s a requirement when creating HTML elements via a map (&lt;em&gt;“Warning: Each child in a list should have a unique ‘key’ prop”&lt;/em&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing the length parameter
&lt;/h3&gt;

&lt;p&gt;The next step is to pass Row the length parameter. As a reminder, the &lt;strong&gt;row&lt;/strong&gt; is a part of the &lt;strong&gt;grid&lt;/strong&gt; component, and the &lt;strong&gt;grid&lt;/strong&gt; is a part of the &lt;strong&gt;Wordle&lt;/strong&gt; component. The grid doesn’t know what the final solution is, it just receives guesses. The Wordle component has the solution, and therefore — access to &lt;code&gt;solution.length&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Technically, we &lt;em&gt;can&lt;/em&gt; use &lt;code&gt;currentGuess.length&lt;/code&gt; and get the same information, with no dependency on the Wordle component passing the parameter. You can still do so. I preferred having one source of truth — &lt;code&gt;solution.length&lt;/code&gt;, because I think it’s cleaner.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Note that there are two instances of Row in the grid component! (lines 4,6 above)&lt;/p&gt;

&lt;p&gt;Cool, that was simple. The next place that has an explicit 5 reference is &lt;code&gt;useWordle.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useWordle Hook
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Here we have a pretty easy swap as well because this hook also has access to the &lt;code&gt;solution&lt;/code&gt; variable. Note that on line 6 above, the &lt;code&gt;5&lt;/code&gt; is for the amount of &lt;strong&gt;turns&lt;/strong&gt;, not the amount of &lt;strong&gt;characters&lt;/strong&gt;! See? I told you &lt;strong&gt;&lt;em&gt;replace-all&lt;/em&gt;&lt;/strong&gt; wouldn’t work here ;)&lt;/p&gt;

&lt;p&gt;For the rest of the occurrences, we carefully replace them with a new variable: &lt;code&gt;let solution_length = solution.length&lt;/code&gt;. Notice that on line 14 below, you’ll need to swap the message string from &lt;code&gt;‘word must be 5 chars.’&lt;/code&gt; to &lt;code&gt;word must be ${solution_length} chars.&lt;/code&gt;. This includes both inserting a variable and changing from single quotes (&lt;code&gt;‘&lt;/code&gt;) to backtick characters.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Last but not least — the styling!&lt;/p&gt;

&lt;h2&gt;
  
  
  The CSS file
&lt;/h2&gt;

&lt;p&gt;If you skip this part, you’ll notice it pretty quickly, when only the first 5 tiles flip beautifully and the others just hitchhike with the first one.&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%2Fvu1mx5xuw8xhtnx9xlv5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvu1mx5xuw8xhtnx9xlv5.gif" alt="The first 5 tiles flip beautifully and the others just hitchhike with the first one" width="240" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To fix this, we need to add animation to the siblings that come after 5:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You might consider changing the delay. If, for example, you have 15 characters — the flipping as it is now (0.2 seconds for each tile) will take 3 seconds, and that’s a long time to wait. You can modify the delay gap to be 0.1 seconds between tiles.&lt;/p&gt;

&lt;p&gt;Moreover, it’s not straightforward to determine the amount of &lt;code&gt;.row &amp;gt; div:nth-child(X){…}&lt;/code&gt; to put. Even when we unlock the wonderous world of longer solutions, there is a limit to how many characters you’ll give your user. Don’t forget we want this game to be mobile-friendly! For my game, I chose a limit of 13 characters (and the Swifties among you know why ;)).&lt;/p&gt;

&lt;p&gt;By the way, the length limit on the word won’t be done here — it will be done in the data set. By that I mean your data set should contain only “correct” solutions, and you shouldn’t do checks for appropriate length in the game’s code. The only hint of the character limitation in the game would be here, in the number of tile animations in the CSS file.&lt;/p&gt;

&lt;p&gt;That’s it! The rest of the code fits this feature “for free”. Tiny changes gained you the cool ability to have longer words. This is setting the ground for the juicy part of the game (both for coding the game and playing it) — the multi-word solutions!&lt;/p&gt;

&lt;p&gt;A comic relief:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/4QPGAJFI_Vc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Adjust to Multi-Words Solutions
&lt;/h1&gt;

&lt;p&gt;We currently can contain an “unlimited” amount of characters for our solutions, so what difference does it make if the solution is one word or more? Ooh! It makes a big difference, which lies in the split!&lt;/p&gt;

&lt;p&gt;To make it easier to understand, let’s follow this tutorial with the example solution “Shake it off” in our minds. Shall we start?&lt;/p&gt;

&lt;h2&gt;
  
  
  Define The Split
&lt;/h2&gt;

&lt;p&gt;There are several approaches to achieve the same goal. I chose the one that would be less disruptive to the existing game logic. By that, I mean that I chose to look at the solution term as one long word (with no spaces) and save next to it an array of the splits in the word, based on this new representation. It sounds complex, I know, so let’s look at an example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“Shake it off” -&amp;gt; “shakeitoff”, [4,6]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Think of it this way: If I wanted to go back from “shakeitoff” to the real song title, after which indexes would I need to enter spaces? After indexes 4 and 6:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Shake it off
    01234 56 789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Needless to say, song titles with a single word, will have an empty split array &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hold on! No need to manually start inserting split arrays into your data set!! At this point, you can choose one of two options: Prepare your data set to contain &lt;code&gt;split&lt;/code&gt;, or calculate it “on the spot” when you gather the solution. I chose the first approach, and in my next blog post, I’ll share how I did it 😀&lt;/p&gt;
&lt;h2&gt;
  
  
  Introducing The Split Property
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, our dataset structure changes from this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    "solutions": [
        {"id": 1, "word": "cupofcode"}
      ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;to this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    "solutions": [
        {"id": 1, "word": "cupofcode", "split":"[2,4]"}
      ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To introduce the split in the code, we’ll go to the same file where we get our &lt;code&gt;solution&lt;/code&gt; value: App.js. We’ll process &lt;code&gt;split&lt;/code&gt; the same way we did &lt;code&gt;solution&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Now, keep in mind that removing whitespaces from a string is easier than inserting them, even if you have a split array. This is the reason why I chose to save the solution term with spaces, and then in the &lt;strong&gt;Wordle component&lt;/strong&gt; have a variable &lt;code&gt;solutionWithoutSpaces = solution.replace(/\sg/,””);&lt;/code&gt;. This is the value I use in the &lt;strong&gt;grid component&lt;/strong&gt; and the &lt;strong&gt;useWordle hook&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The grid component needs the split parameter just so it can pass it to the row component. The row component is where the magic happens!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Row Component
&lt;/h2&gt;

&lt;p&gt;This component can return three different types of rows: One occupied with a past guess, one filled (fully or partly) with the current guess the user is typing, or one empty from letters.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We’ll start with the initial case: The empty row.&lt;/p&gt;

&lt;h3&gt;
  
  
  The empty row (lines 26–30 above)
&lt;/h3&gt;

&lt;p&gt;With the solution “Shake it off” in mind, let’s think about what we want this Row to return. Our split array is &lt;code&gt;[4,6]&lt;/code&gt;— This means we want space after the 4th and 6th tiles. This space will be achieved by another type of div &lt;em&gt;class&lt;/em&gt;, which I named &lt;code&gt;space-div&lt;/code&gt; with a small width and a zero-sized border (to override the border set for &lt;code&gt;.row &amp;gt; div&lt;/code&gt; class).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    .row &amp;gt; .space-div {
      width: min(2vw,20px);
      border: 0px solid #bbb;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We already have a map iterating over the character tiles, so all we have left to do is &lt;strong&gt;add&lt;/strong&gt; the new &lt;code&gt;div&lt;/code&gt; only if it fits the condition “Is the index in the split array?”&lt;/p&gt;

&lt;p&gt;This is how it will look like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
      split.includes(i) &amp;amp;&amp;amp;
      &amp;lt;div 
        key={`${i}_seperator`}
        className=”space-div”&amp;gt;
      &amp;lt;/div&amp;gt;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Is that it? Almost! If you try to add this block below the existing &lt;code&gt;&amp;lt;div key={i}&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; (on line 28), you will encounter an error: &lt;code&gt;“JSX expressions must have one parent element”&lt;/code&gt;. This is easily solved by wrapping those two divs with &lt;strong&gt;&lt;em&gt;&lt;/em&gt;&lt;/strong&gt;. Now they have a parent!&lt;/p&gt;

&lt;p&gt;Most times you don’t even need to use the full Fragment syntax, and can just use the shorter version: &lt;code&gt;**&amp;lt;&amp;gt;**..code..**&amp;lt;/&amp;gt;**&lt;/code&gt;. In our case, we do need to use the full Fragment syntax because we need to add a key (remember from earlier? The map function requires adding a key!), and you can’t do it with the short syntax.&lt;/p&gt;

&lt;p&gt;With all that, our code looks like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Note that we have two divs and a fragment in the map, so we give each one a unique key: &lt;code&gt;${i}_frag&lt;/code&gt;, &lt;code&gt;{i}&lt;/code&gt; and &lt;code&gt;${i}_seperator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was the first Row case — an Empty row. We took the initial map callback function, and wrapped it in a React fragment that contains a separator div when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   &amp;lt;React.Fragment key={`${i}_frag`}&amp;gt;
     &amp;lt;INITIAL_DIV_RETURNED&amp;gt;
     {
      split.includes(i) &amp;amp;&amp;amp; 
      &amp;lt;div key={`${i}_seperator`} className=”space-div”&amp;gt;
      &amp;lt;/div&amp;gt;
     }
    &amp;lt;/React.Fragment&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;We will use the same fragment structure in the other row scenarios as well.&lt;/strong&gt; The next case, in chronological order, is the current row (that the user is typing into).&lt;/p&gt;
&lt;h3&gt;
  
  
  The current row (lines 15–22 above)
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;&amp;lt;div className=’row current’&amp;gt;&lt;/code&gt; , there are two arrays: One for the letters the user entered in the current guess, and one for filling in the empty tiles to match the length of the solution word.&lt;/p&gt;

&lt;p&gt;In that second array, where it iterates &lt;code&gt;length-letters.length&lt;/code&gt; times, make sure you use &lt;code&gt;letters.length + i&lt;/code&gt; value to check if the index is split or not. Let’s see it in the code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  The past row
&lt;/h3&gt;

&lt;p&gt;At this point, it’s easy peasy. We take the React fragment, insert the original div, and get:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Cool, all the rows are modified. Off to the next component!&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%2F56mw3tedznmuyyzbeajg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56mw3tedznmuyyzbeajg.png" alt="Three types of rows: Past, current, and empty." width="790" height="903"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The useWordle Hook
&lt;/h2&gt;

&lt;p&gt;You’d expect this file to contain some big changes. However, because we passed the &lt;code&gt;solutionWithoutSpaces&lt;/code&gt; parameter, the hook doesn’t even know there were spaces in the original solution!&lt;/p&gt;

&lt;h2&gt;
  
  
  The CSS file
&lt;/h2&gt;

&lt;p&gt;This only needs to be updated to accommodate the changes made in the Row component. In my case, it means adding animation delays up until the 13th child (the spaces from the split array are also &lt;code&gt;.row &amp;gt; div&lt;/code&gt;). This is, of course, in addition to adding the style for the “space-div” class:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  The Share String
&lt;/h2&gt;

&lt;p&gt;This section is only relevant to those who followed the tutorial in &lt;a href="https://cupofcode.blog/wordle-in-react-part-2/" rel="noopener noreferrer"&gt;my previous blog post&lt;/a&gt;. There, we created the squares string that the user gets when they click the “share” button after the game is done. This string represents the solution characters, which means we need to add spaces!&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%2Fbvdrxlhscdc9lf6gnzcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvdrxlhscdc9lf6gnzcs.png" alt="*Users want to show off their success and send their friends their game process*" width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do so, all we need to add is a tiny &lt;code&gt;if&lt;/code&gt; condition. For each guess, we assign to every letter the appropriate square color. With the letters, we can also check the index!&lt;/p&gt;

&lt;p&gt;That way, after adding the right colored square, we check: If the index is in the &lt;code&gt;split&lt;/code&gt; array, it means there should be a space after this square, so we add it to the string:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Comic relief: &lt;em&gt;Because life is too short to pretend you don’t like Taylor Swift songs :)&lt;/em&gt;&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Bonus: External Data Set
&lt;/h1&gt;

&lt;p&gt;I &lt;strong&gt;&lt;em&gt;really&lt;/em&gt;&lt;/strong&gt; wanted the data set to sit outside the project. Firstly, I wanted to place an extra step for the curious user who checks the browser’s developer tools to see the bank of words. Secondly, and most importantly, I didn’t want to rebuild and upload the whole project every time Taylor Swift released a new album (who else is excited about &lt;a href="https://twitter.com/taylorswift13/status/1754332182401691704" rel="noopener noreferrer"&gt;&lt;em&gt;The Tortured Poets Department&lt;/em&gt;&lt;/a&gt; releasing in April??).&lt;/p&gt;

&lt;p&gt;By having the data set in a different location, I decouple it from the main project, which is always a good practice — why create a dependency where there isn’t one?&lt;/p&gt;

&lt;p&gt;Officially,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Decoupled architecture is an architectural approach that allows each computing component to exist and perform tasks independently of one another, while also allowing the components to remain completely unaware and autonomous until instructed. — &lt;a href="https://www.indicative.com/resource/decoupled-architecture/#:~:text=Decoupled%20architecture%20is%20an%20architectural,unaware%20and%20autonomous%20until%20instructed." rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Wordlesturck’s case, it’s even better than just generic loose coupling because the data set is created in a different project (more about it in my next blog post :D), so I can update the file (and therefore the game) without touching the Wordle project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating The External Data Set
&lt;/h2&gt;

&lt;p&gt;Currently, our data set file is called &lt;code&gt;db.json&lt;/code&gt; and it sits in the &lt;code&gt;data/&lt;/code&gt; directory. We’ll start by creating a new project, outside of the Wordle one, that will have that same &lt;code&gt;db.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;besides the JSON file, you’ll need a &lt;code&gt;netlify.toml&lt;/code&gt; file with the following content:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;netlify.toml&lt;/code&gt; is a configuration file that specifies how Netlify builds and deploys your site. More about it &lt;a href="https://docs.netlify.com/configure-builds/file-based-configuration/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lines 1–4 redirect &lt;a href="https://react-wordle-db.netlify.app" rel="noopener noreferrer"&gt;https://react-wordle-db.netlify.app&lt;/a&gt; to &lt;a href="https://react-wordle-db.netlify.app/db.json" rel="noopener noreferrer"&gt;https://react-wordle-db.netlify.app/db.json&lt;/a&gt;. Your code will work without it, I just find it nicer to see &lt;em&gt;something&lt;/em&gt; and not an error when I click the link, as the developer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lines 5–8 are needed to make it work :D Otherwise, when you load your game (the Wordle project), you’ll see a &lt;strong&gt;&lt;em&gt;CORS error&lt;/em&gt;&lt;/strong&gt;.&lt;/p&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%2Fa34asbe20frfh38yw74q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa34asbe20frfh38yw74q.png" alt="CORS error" width="644" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can create a new site on Netlify using this project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling The External Data Set
&lt;/h2&gt;

&lt;p&gt;In our current code, we interact with &lt;code&gt;db.json&lt;/code&gt; in two files: &lt;strong&gt;App.js&lt;/strong&gt; for grabbing the solution, and and &lt;strong&gt;Keypad.js&lt;/strong&gt; for pulling the letters.&lt;/p&gt;

&lt;p&gt;As I mentioned in a previous blog post, I am strongly against saving the letters in the db file (more about it &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/#Keyboard" rel="noopener noreferrer"&gt;here&lt;/a&gt;). I prefer having them in the keypad component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    export default function Keypad() {
      const topRow = ["q","w","e","r","t","y","u","i","o","p"]
      const middleRow = ["a","s","d","f","g","h","j","k","l"]
      const bottomRow = ["z","x","c","v","b","n","m"]
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So, I’ll be ignoring that part and focusing on the occurrence in &lt;strong&gt;App.js&lt;/strong&gt;. But don’t worry, after this one, the keypad modification will be easy for you to do by yourselves.&lt;/p&gt;

&lt;p&gt;Let’s start by observing the current code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;We’ll start by changing line 4 to fetch the file from our new location: &lt;a href="https://react-wordle-db.netlify.app" rel="noopener noreferrer"&gt;https://react-wordle-db.netlify.app&lt;/a&gt;. Note that we are not pulling the &lt;strong&gt;solutions&lt;/strong&gt; JSON specifically (AKA we don’t end with &lt;code&gt;/solutions&lt;/code&gt;), but the whole file. Therefore, we need to adjust line 8 to address the solutions part of the JSON response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const randomSolution = json.solutions[
      Math.floor(Math.random()*json.solutions.length)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will work. With that said, to keep our code quality high, I recommend adding some error-catching to this fetch call. We didn’t need it before because the file was local, but that’s not the case anymore.&lt;/p&gt;

&lt;p&gt;So, to make the project as durable as possible, I chose to keep the original &lt;code&gt;db.json&lt;/code&gt; file inside the project and &lt;a href="https://en.wikipedia.org/wiki/Graceful_exit" rel="noopener noreferrer"&gt;gracefully fail&lt;/a&gt; into it if my db website had an oopsie.&lt;/p&gt;

&lt;p&gt;Note that the original file will soon become “the old version”. This is ok because due to the game’s nature, the user probably won’t notice it: They receive only 1 solution per day, in random order.&lt;/p&gt;

&lt;p&gt;This is how we’ll do it:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Just like the &lt;code&gt;.then(...)&lt;/code&gt; function, we’ll add a &lt;code&gt;.catch(...)&lt;/code&gt;. This catch will contain the same code block that is in lines 5–12 above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For this to work, we’ll need to declare &lt;code&gt;randomSolution&lt;/code&gt; outside the fetch, in line 4.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we test our error handling, there are two additional changes we should make.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We Don’t Need Fetch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please note that you don’t need to use &lt;code&gt;fetch(...)&lt;/code&gt; for local files. &lt;em&gt;The base code&lt;/em&gt; had fetch because the developer was using the &lt;code&gt;json-server&lt;/code&gt; package which acts like an external API server. As a result, The code was using the fetch function to grab the json.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json-server&lt;/code&gt; won’t work when you upload your website to Netlify, so I decided not to use it. Instead, we can simply do the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We’ll start by importing the JSON using a simple import on line 1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then, just like before, because we don’t have a specific API call with &lt;code&gt;/solutions&lt;/code&gt;, we turn any &lt;code&gt;json&lt;/code&gt; occurrence to &lt;code&gt;json.solutions&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next thing that would be nice to do is export the duplicate code to a function:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Notice that even though both calls to the &lt;code&gt;setSolutionRelatedStates()&lt;/code&gt; function are with the &lt;code&gt;json.solutions&lt;/code&gt; parameter, one is referencing the &lt;code&gt;json&lt;/code&gt; variable from &lt;code&gt;.then(json =&amp;gt; {...}&lt;/code&gt; (line 17 above) and one references the &lt;code&gt;json&lt;/code&gt; variable from &lt;code&gt;import json from ‘./data/db.json’&lt;/code&gt; (line 1).&lt;/p&gt;

&lt;p&gt;Lastly, let’s see the error handling in action:&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%2Fzrg95iazyrwhblnxunxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrg95iazyrwhblnxunxl.png" alt="Error handling is working!" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An easy error would be to add &lt;code&gt;/solutions&lt;/code&gt; to &lt;a href="https://react-wordle-db.netlify.app" rel="noopener noreferrer"&gt;https://react-wordle-db.netlify.app&lt;/a&gt; in the fetch.&lt;/p&gt;

&lt;p&gt;And we’re done! In this blog post, we created a Wordle version that can have solutions longer than 5 characters, and multi-word solutions. We also set up an external location for the data set, so it will be decoupled from the main project.&lt;/p&gt;

&lt;p&gt;To theme your wordle, all there is left to do is choose an appropriate name and logo, and gather the right data set. I will show you how I created mine in the next blog post :)&lt;/p&gt;

&lt;p&gt;And for the Swifties out there, my game will launch VERY SOON! &lt;a href="https://www.linkedin.com/in/ifatneumann/" rel="noopener noreferrer"&gt;Connect with me on Linkedin&lt;/a&gt; to get notified when it’s online! :D&lt;/p&gt;

&lt;p&gt;See you soon!&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :) Thank you for your support!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Wordle in React: Picking Up Where We Left Off!</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sun, 11 Feb 2024 23:48:28 +0000</pubDate>
      <link>https://forem.com/cupofcode/wordle-in-react-picking-up-where-we-left-off-39j6</link>
      <guid>https://forem.com/cupofcode/wordle-in-react-picking-up-where-we-left-off-39j6</guid>
      <description>&lt;h3&gt;
  
  
  The flipping tiles are cool, but why stop there? In this tutorial, we’ll create all the other cool features that are in the original game!
&lt;/h3&gt;

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

&lt;p&gt;Welcome to part 2 of my Wordle tutorial! In &lt;a href="https://cupofcode.blog/wordle-in-react-part-1/" rel="noopener noreferrer"&gt;the previous blog post&lt;/a&gt;, I gave beginner’s tips and tricks in React, and we worked on creating guess distribution, landing page, and game index. You should start there because the features we will work on today depend on those!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2106%2F1%2ATbGR65C-IANQxWuKSybFvQ.png" alt="[https://cupofcode.blog/wordle-in-react-part-1/](https://cupofcode.blog/wordle-in-react-part-1/)" width="800" height="730"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cupofcode.blog/wordle-in-react-part-1/" rel="noopener noreferrer"&gt;https://cupofcode.blog/wordle-in-react-part-1/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this blog post, we will continue developing the features that exist in the original game, but for some reason were left out of the common “Wordle in React” tutorials online.&lt;/p&gt;

&lt;p&gt;I am also planning a last, but not least, part for this series — how to modify the game to play with words different than 5 letters long!&lt;/p&gt;

&lt;p&gt;The agenda for today is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The share button&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A different view depends on the user’s game state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The progress modal — adding streak!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bonus: A shaky row!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final touches&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Are you ready? :D&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%2F0z6wjz0oebi6l1gn5zn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z6wjz0oebi6l1gn5zn5.png" width="551" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Share Button&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We are going back to the progress modal!&lt;/p&gt;

&lt;p&gt;After they finish the game, our users want to show off their success and send their friends their game process. In the original game, when you click the share button, it copies to your clipboard a string of squares that are a visual representation of the guesses in the game. Then, you can paste that string anywhere you want: WhatsApp, Twitter, etc.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fbvdrxlhscdc9lf6gnzcs.png" alt="Users want to show off their success and send their friends their game process" width="800" height="705"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Users want to show off their success and send their friends their game process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We will build this feature in 2 steps: First, create the string of squares along with the game name, game number, and the number of guesses it took. Then, we add the “copy to clipboard” functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create The Squares String
&lt;/h3&gt;

&lt;p&gt;There are two places you can create that squares-string in, and they both make sense. The first one is in the useWordle.js hook, right next to where we add a new guess to the guesses array: Adding a new guess— adding a new string of squares.&lt;/p&gt;

&lt;p&gt;The second option is adding this code where the user requests the string: In the Share button’s “onClick” function. That way, we execute this code only when we need it. This approach is cleaner, but the problem with it is that we don’t currently have access to the guesses via the modal component, and passing them just for this feature seems like a major modification.&lt;/p&gt;

&lt;p&gt;So, I went with the first approach. In the useWordle.js hook, similar to guesses state, I created squearesStrings state &lt;strong&gt;with a very similar logic&lt;/strong&gt;: Based on the previous squares-strings array, create a new array and add the new guess.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Translating the guess into squares string is pretty straightforward, so we’ll move on and focus on the juicy parts instead :D&lt;/p&gt;

&lt;p&gt;Now that we have an array of the squares representing the guesses, we’ll &lt;strong&gt;pass it&lt;/strong&gt; to the modal and &lt;strong&gt;stringify it&lt;/strong&gt; there. At this point, we will create the resultString variable, which includes the rest of the information that is shared in the message: The Game name, the game number (which we need to pass as a parameter as well), and the number of turns it took.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This worked just fine, so what made me change to the second approach? The answer to that depends on future functionality, so we’ll revisit this one later.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. “Copy to Clipboard” Functionality
&lt;/h3&gt;

&lt;p&gt;This is a cool feature that it’s implementation is easier than I thought!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.w3schools.com/css/css_tooltip.asp" rel="noopener noreferrer"&gt;w3schools tutorial&lt;/a&gt; is very clear, the only modification we need is to copy our &lt;strong&gt;resultString&lt;/strong&gt; variable and not a text box like they are showing in their demo. Our line of code will be: navigator.clipboard.writeText(&lt;strong&gt;resultString&lt;/strong&gt;);&lt;/p&gt;

&lt;p&gt;Our code will look like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And in index.css:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I usually don’t share CSS code, because styling is an individual taste. However, this time it contains functionality. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;v.tooltip .tooltiptext::after {content: ""; ...} will initialize the text after the copy, so if the user clicks the button twice, they won’t get double the content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The visibility property will ensure the tooltip appears when the share button is clicked and disappears when the mouse is not hovering above the button anymore.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2F7kyxmyiztutvz6ijh6xk.png" alt="Copy to clipboard functionality" width="800" height="395"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copy to clipboard functionality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A Different View Depends on The User’s Game State
&lt;/h2&gt;

&lt;p&gt;In the previous blog post, we created the grey &lt;em&gt;“Welcome”&lt;/em&gt; landing page. Have you ever tried going to the Wordle website after you already played that day in that browser? If you do so, you will see a &lt;em&gt;“Hey, great job today!”&lt;/em&gt; page.&lt;/p&gt;

&lt;p&gt;While working on this project, I visited the Wordle website &lt;strong&gt;a lot&lt;/strong&gt;. I had to see what’s the scope of every feature and test how it responds to edge cases. It was by accident that I discovered there is a third landing page — the &lt;em&gt;“you’re mid-game, come continue”&lt;/em&gt; page!&lt;/p&gt;

&lt;p&gt;The different view is not only on the landing page. When the user clicks the button, they see their previous guesses on the grid and the colored keys on the keyboard.&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%2F910p6qtmhz5w4yzlhjz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F910p6qtmhz5w4yzlhjz4.png" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A quick recap:&lt;/p&gt;

&lt;p&gt;Our game is &lt;em&gt;a single-page application&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A single-page application is a website that interacts with the user by dynamically rewriting the current web page with new data from the web server, instead of the default method of a web browser loading entire new pages.” &lt;a href="https://en.wikipedia.org/wiki/Single-page_application" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our case, we don’t use a server-side, but you get the idea. This means that depending on booleans, we show and hide different components: We start with showWordle=false , which makes the user see a grey landing page with two buttons - &lt;strong&gt;Play&lt;/strong&gt; and &lt;strong&gt;How to Play&lt;/strong&gt;. When they click “&lt;strong&gt;Play&lt;/strong&gt;”, we set the Wordle boolean (showWordle) to true (line 9 below). This makes the Wordle component (the page with the grid and the keyboard) show up and the landing page hides (lines 6,15 below).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When the user clicks “&lt;strong&gt;How to Play&lt;/strong&gt;”, they will see the instructions modal &lt;strong&gt;&lt;em&gt;above&lt;/em&gt;&lt;/strong&gt; the Wordle component. This means that you can see the grid page in the modal’s background and closing the modal will reveal the page fully. To achieve that, we need another boolean (line 3 below) to indicate the instructions modal should &lt;strong&gt;show&lt;/strong&gt; as well:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Whether the wordle boolean or the instructions boolean are true (depending on which button the user clicked) — we show the wordle component (line 16 above).&lt;/p&gt;

&lt;p&gt;Recap is over, now we can start!&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%2Fxrrbx50t08nej4xdsvd3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrrbx50t08nej4xdsvd3.gif" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Post-Game Welcome Page
&lt;/h3&gt;

&lt;p&gt;If we look at the welcome component, the only difference between the two grey pages is the title, description, and button(s).&lt;/p&gt;

&lt;p&gt;The indication of which page to show depends on whether the user played today, which is a boolean! We’ll call our boolean gameWasPlayedToday and for now — set it to false (line 3 below). We use this boolean to wrap our existing landing page code (line 8) and the post-game landing page code (lines 14–20).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The post-game welcome page has one button — &lt;em&gt;see stats&lt;/em&gt;. &lt;strong&gt;Similar to showing the instructions modal&lt;/strong&gt;, we need to create a boolean and use it for the logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On line 2 we initialize showProgress to false.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 17 we set it to true on the button’s onClick function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On lines 6,25 we add this boolean to the page’s conditions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 29 we pass the boolean to the Wordle component, which is the one showing/hiding the modal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, all that is left here is to set gameWasPlayedToday correctly (line 3). How do we know if the user played today? We have local storage for statistics and guess distribution— could that be useful? Yes and no.&lt;/p&gt;

&lt;p&gt;The data we save in statistics do not currently indicate if the user played today. We can modify this object to save more information, but I think it’s already big enough, and prefer creating a new local storage object: localStorage.wordlestruckLastGamePlayed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a new local storage object&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are five things we need to know about the last game played:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;gameNum will help us know if the last game played was &lt;strong&gt;today&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;turn, isCorrect will be used to color the correct &lt;a href="https://cupofcode.medium.com/wordle-in-react-part-1-39aad8755ea1#1123" rel="noopener noreferrer"&gt;guess distribution bar&lt;/a&gt; in green.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;guesses, usedKeys will be used to populate the grid and the keyboard.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where does our code determine when a game is over? In the Wordle component’s useEffect! That’s where we will &lt;strong&gt;set&lt;/strong&gt; our new local storage object:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now we can go back to the landing page component and set gameWasPlayedToday dynamically: We start by setting lastGame to our new localStorage variable (or undefined if there isn’t any). Then we determine if the user played today by comparing the saved game index with today’s game index.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Note that there are two ways to populate gameWasPlayedToday, and you’ll see both in my code. When it’s only one variable dependent on whether last game is defined, I use the inline if, and add lastGame &amp;amp;&amp;amp; ...:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var gameWasPlayedToday = lastGame &amp;amp;&amp;amp; lastGame.gameNum == index ? true : false;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If there are several variables, I prefer initializing them first and then modifying them in an if block:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var gameWasPlayedToday = false;
If (localStorage.wonderstruckLastGamePlayed){
  let lastGame = JSON.parse(localStorage.wonderstruckLastGamePlayed)
  gameWasPlayedToday = lastGame.gameNum == index ? true : false;
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;So, we used the stored game index, and we have 4 more variables to utilize: turn, isCorrect, guesses, and usedKeys. We’ll apply the turn and isCorrect variables to update the statistics component.&lt;/p&gt;

&lt;p&gt;The post-game landing page has a “&lt;em&gt;see stats&lt;/em&gt;” button that opens the progress modal (which is calling the statistics component).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fg62asa02lkybt7nz3xnj.png" alt="Post-game functionality. (Don’t worry, I’ll shuffle the solutions before launching the game ;) )" width="677" height="426"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post-game functionality. (Don’t worry, I’ll shuffle the solutions before launching the game ;) )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Up until this point, the turn and isCorrect variables were passed through the component’s parameters, ‘in real time’ when the game was over. We will now pull that data from the local storage variable:&lt;/p&gt;

&lt;p&gt;We’ll start with an if condition to check if there is stored data for a previous game played, and initialize the variables for a case there isn’t (this is the “second way to initialize variables based on lastGame" that I mentioned earlier :D).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Did you notice something extra on line 12?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even with the new landing page, we only reach the progress modal when the game is over. So, why did I add gameWasPlayedToday &amp;amp;&amp;amp; ... to the condition for coloring barColor? This is not part of this feature, but part of a new one that I will give you the pleasure of adding yourselves: The header buttons.&lt;/p&gt;

&lt;p&gt;The original Wordle has buttons in the main page’s header, that give you access to the instructions and the statistics modals &lt;strong&gt;at any time&lt;/strong&gt;. This means the user can start a new game, and decide they want to check their statistics before the game is over.&lt;/p&gt;

&lt;p&gt;In that case, we want all the bars to be grey (and not green), and in my game — I also hide the solution word. In addition, I chose to hide the share button, but this is a personal preference.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fgj4btpwapipn4w3r5b22.png" alt="The Progress modal before the game is over: All the bars are grey and there is no share button" width="476" height="643"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The Progress modal before the game is over: All the bars are grey and there is no share button&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We already have the functionality to show those modals thanks to the various landing pages, so all that is left to do is create two buttons and setShowProgressModal(true)/ setShowInstructionsModal(true) on click!&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Showing guesses and used keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We made nice progress, but we still didn’t cover all the functionality required for the post-game landing page.&lt;/p&gt;

&lt;p&gt;When the user clicks the &lt;em&gt;See Stats&lt;/em&gt; button, it will open the progress modal, which we took care of. When the user closes that progress modal, they should see all their past guesses on the grid, but we don’t currently have that information. So… we’ll add it now!&lt;/p&gt;

&lt;p&gt;We already have guesses and usedKeys saved in localStorage.wordlestruckLastGamePlayed object, so all that is left to do is add a view option so that if the user already played today, we’ll populate the data with the stored object.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On lines 3–10 below, we populate gameWasPlayedToday, gameData.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On lines 23–31 is the good old Wordle main page — the grid and keypad components, wrapped with the condition of !gameWasPlayedToday &amp;amp;&amp;amp; ....&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On lines 14–22, we also have the grid and keypad components but this time we populate the properties guesses, turn, and usedKeys with the data from the stored object. And of course, we wrap this block with gameWasPlayedToday &amp;amp;&amp;amp; ...&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Huston, we have a problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another issue that comes up now is that our new shiny share button will be accessible but without a squares-string! This is because we create the squares-string in the useWordle.js hook, but we don’t interact with this part when opening the progress modal &lt;strong&gt;directly from the landing page&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I know, we are jumping back and forth, and it must be confusing! No need to scroll up, I’ll give you all the context you need: To let our users show off their game skills, we created squares string representing the game guesses (without exposing the word!).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fih26ib86jm3jyxc9q9am.png" alt="Please don’t take away my Swiftie badge" width="365" height="450"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Please don’t take away my Swiftie badge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We talked about two places we can build that string and chose to go with the useWordle.js hook, right after we add a new guess to the guesses array. This is because we dismissed the second option, which is within the share button’s on-click function. We didn’t have access to the guesses via the progress modal component, and passing them just for this feature was too much hustle for the same result.&lt;/p&gt;

&lt;p&gt;Well, guess what? We have access to the guesses array from wherever we want, thanks to the localStorage.wordlestruckLastGamePlayed object! Let’s modify the code to create the share-string in the progress modal component.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The guesses array in the useWordle.js hook is initialized to be of size 6 (because there are max 6 guesses), and the guesses are added every turn. This means we can’t simply iterate over the guesses array (guesses.forEach((formattedGuess) =&amp;gt; { ...), we need to ensure we iterate only over the populated cells (if(formattedGuess) { ...).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now we can initialize resultString directly with the prefix, instead of adding it &lt;em&gt;after&lt;/em&gt; the squaresString creation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At this point, you can remove any code related to the previous version of squaresString we added in useWordle.js, Wordle.js and ProgressModal.js.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-deserved meme break:&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%2Ftpga09q2i5seim3m04g5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpga09q2i5seim3m04g5.png" width="451" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mid-Game Welcome Page
&lt;/h3&gt;

&lt;p&gt;If the user loads the page after entering guesses but before finishing the game, they will see the mid-game welcome page.&lt;/p&gt;

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

&lt;p&gt;This page has one button — &lt;em&gt;Continue&lt;/em&gt;. When the user clicks it, they see the main page with the guesses they tried so far and the keys they used. To achieve that, we can’t save the game data only &lt;strong&gt;when the game ends&lt;/strong&gt; — we need to save the relevant information &lt;strong&gt;after every round&lt;/strong&gt; ( = after every guess the user entered)! We’ve seen usage of local storage before, but this one is different!&lt;/p&gt;

&lt;p&gt;You can either trust me or try yourselves (I encourage both approaches ;) ), but updating local storage directly won’t work. This is because the Wordle.js component renders a lot more times than you might think —in fact, we trigger page rendering at every key press (due to the handlekey listener). For that reason, if we were to try to continue the game, the typing would cause the data of previous guesses to initialize to an empty array.&lt;/p&gt;

&lt;p&gt;The right approach is to add to useWordle.js hook a new state variable: storageData. We will set this variable after every guess entered, and then in Wordle.js we will update local storage with it. That way, in those handlekey renders, the data we set in the useWordle.js hook stays as it was. It only updates on the submission of valid guesses, when the code reaches return {turn, currentGuess, guesses, ...}.&lt;/p&gt;

&lt;p&gt;Let’s start coding!&lt;/p&gt;

&lt;p&gt;We will begin by initializing variables for each data needed in the hook: initialX, when X is game number, turn, etc. The value will be pulled from gameWasPlayedToday or restarted, if there isn’t any data saved (lines 3–8 below). Then, we will create the new variable storageData and initialize it with those variables (lines 10–17).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Please note that to continue the game &lt;strong&gt;at any point&lt;/strong&gt;, we need to update our localStorage.wordlestruckLastGamePlayed object to contain two more properties: history and isCorrect. This is because we need those for the useWordle hook functionality.&lt;/p&gt;

&lt;p&gt;A quick reminder: The difference between history and guesses is that history is a simple array of strings, while guesses is an array of &lt;em&gt;formatted&lt;/em&gt; guesses, where each character in each guess is colored.&lt;/p&gt;

&lt;p&gt;All that is left to do is add the setter for storage data to addNewGuess() function. We will set the properties just like we set their original counterparts (lines 11–49 below).&lt;/p&gt;

&lt;p&gt;Lastly, we will return storageData to the Wordle component (line 52).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Are you seeing what I am seeing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This code is a bit &lt;strong&gt;painful&lt;/strong&gt; to read because setStorageData contains all the previous sets’ code: setIsCorrect, setGuesses, setHistory, setTurn and SetUsedKeys.&lt;/p&gt;

&lt;p&gt;Code duplication is bad practice because now you have multiple places to update code and you might not remember all of them. How about modifying useWordle.js to have &lt;strong&gt;only&lt;/strong&gt; setStorageData? We will do that later, once storageData is up and running, and the application is using it exclusively. We still need to use the data we collected!&lt;/p&gt;

&lt;p&gt;In Wordle.js , just above the end of the useEffect scope, we update the local storage with storageData:&lt;br&gt;
localStorage.wordlestruckLastGamePlayed = JSON.stringify(storageData);&lt;/p&gt;

&lt;p&gt;Lastly, we need to put the correct boolean to show the mid-game landing page.&lt;/p&gt;

&lt;p&gt;A quick reminder: If the game hasn’t started yet — we want to show the “welcome” landing page. If the game started but is still in progress, we want to show the “welcome back” page, and if the user finished today’s game, we will show the “good job!” page:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We already set gameWasPlayedToday variable when we worked on the post-game landing page:&lt;/p&gt;

&lt;p&gt;gameWasPlayedToday=&lt;br&gt;
lastGame &amp;amp;&amp;amp; lastGame.gameNo === gameNo ? true : false;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This now needs to change&lt;/strong&gt;. We modified the code to save state after every turn, therefore the game index in lastGame indicates this game has started, not necessarily that it's done.&lt;/p&gt;

&lt;p&gt;If gameWasStartedToday is the new gameWasPlayedToday, how do we gather the value for gameWasPlayedToday? No need to save additional variables in local storage, we can just calculate it from the existing stored data:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We know the game &lt;strong&gt;started&lt;/strong&gt; today if the last game’s index is today’s game index. To check if the game was &lt;strong&gt;done&lt;/strong&gt; — we use the same condition as in the Wordle.js component — if isCorrect is true or if the user played their 6th guess.&lt;/p&gt;

&lt;p&gt;In case you wondered, I am saving the turn value in a local variable because I need it for the work-in-progress landing page — we want to show the user how many tries they already made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the Progress Modal!
&lt;/h2&gt;

&lt;p&gt;In the previous blog post, we created the progress modal: It called the statistics component, which shows the user data of games played and % of wins, and below that their guess distribution. This is where we stopped because we needed today’s context to take the modal to the finish line!&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%2Foee6naaspf8ato1cs4g1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foee6naaspf8ato1cs4g1.png" width="698" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug Fix — Update Statistics Only Once
&lt;/h3&gt;

&lt;p&gt;With today’s changes, we have unlimited access to the Wordle component, and more specifically — access to the end-of-game condition: isCorrect===true || turn&amp;gt;5:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We wrote &lt;a href="https://cupofcode.medium.com/wordle-in-react-part-1-39aad8755ea1#df84" rel="noopener noreferrer"&gt;this code in the previous blog post&lt;/a&gt;, go check it if you need a reminder!&lt;/p&gt;

&lt;p&gt;This code has become problematic now because &lt;strong&gt;every time&lt;/strong&gt; we go from the “you-already-played-today” landing page to the grid and keyboard page, the if statement (line 5 above) is correct, and we update the statistics (again) on lines 14–15.&lt;/p&gt;

&lt;p&gt;Therefore, we need to make sure we update the statistics data &lt;strong&gt;only once&lt;/strong&gt; per game. We can do that by wrapping it in another if condition. That, together with the existing if condition (“if the game is finished”), creates a sweet spot that happens only once in the daily game’s life cycle: The state where local storage’s isCorrect or turn&amp;gt;5 is changing value for the first time!&lt;/p&gt;

&lt;p&gt;I created two variables: player_just_won and player_just_lost. Let me remind you we are updating the local storage after every turn. Now, I know the player has &lt;em&gt;just&lt;/em&gt; won if isCorrect is true but JSON.parse(localStorage.wordlestruckLastGamePlayed).isCorrect is still false. Same for when the player &lt;em&gt;just&lt;/em&gt; lost the game: turn&amp;gt;5 but also JSON.parse(localStorage.wordlestruckLastGamePlayed).turn===5.&lt;/p&gt;

&lt;p&gt;It’s also very important to move out the initializing code (lines 7–10 above) to be &lt;strong&gt;before&lt;/strong&gt; the useEffect hook. If you don’t move it, you will encounter a bug later, when you add the statistics button to the header. The bug occurs when the user enters the game for the first time and clicks the statistics button: It will be undefined! Logically, we want localStorage.statistics to be defined when we reach the progress modal. So we’ll define it first thing in the Wordle component.&lt;/p&gt;

&lt;p&gt;Our code will now look something like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Feature — Adding Streaks Data
&lt;/h3&gt;

&lt;p&gt;To give the progress modal the final touch, we can collect the data for the &lt;strong&gt;current strike&lt;/strong&gt; and the &lt;strong&gt;maximum strike&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We’ll start by adding those values to our statistics object, along with currentStreakLastIndex — this is needed to know if the strike continues or not. This means the array of statistics will sit deeper in the object— localStorage.wordlestruckStatistics.stats. We will initialize all the new properties with zero.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Updating the values can be tricky: It’s very important to update them in the correct order! Let’s start with the easier one: maxStreaks is updated only if currentStreak is bigger. we need to check currentStreak‘s value only &lt;strong&gt;after&lt;/strong&gt; we update it today.&lt;/p&gt;

&lt;p&gt;currentStreak will be increase only if today’s game continues the streak, which means if today’s game index is currentStreakLastIndex + 1. Otherwise, it will initialize to be 1. This means we should check currentStreakLastIndex‘s value &lt;strong&gt;before&lt;/strong&gt; we update it today.&lt;/p&gt;

&lt;p&gt;Lastly, currentStreakLastIndex‘s value should be updated to be today’s game index. Remember that we reach this code only when the game is over, so this game is the current streak’s last index.&lt;/p&gt;

&lt;p&gt;Let’s see it in the code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Important note!&lt;/strong&gt; The original game is counting streaks of winning, not streaks of playing. I decided to count the streak of playing the game, regardless if the user managed to guess the correct answer or not. If you wish to count streaks for winning, make sure to check if isCorrect is true before updating the new three variables (currentStreak, maxStreak and currentStreakLastIndex).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you already published your website&lt;/strong&gt; and sent it to your friends (I’ve mentioned &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; as a good platform to host your website for free, in the previous blog post), then you should update the name wordlestruckStatistics to something else. Otherwise, you are referencing in your code the developed version of the object (wordlestruckStatistics.stats for example), but your users have the previous version saved in their local storage, so running wordlestruckStatistics.stats will break the application. When you rename that variable, the code will enter the initializing block (if(!wordlestruckStatistics){…}), and you will save yourself a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus Feature — Shaky Rows!
&lt;/h2&gt;

&lt;p&gt;There is one feature from the original game that I’ve decided to not recreate — the “not a real word” feature. When that happens, the row &lt;em&gt;shakes&lt;/em&gt;, and a black tooltip appears, notifying you that this word doesn’t exist.&lt;/p&gt;

&lt;p&gt;I am still wondering “How did they create a data set of all possible 5-letter words?”. Once you have this data set, it’s pretty easy to notify the user when they have invented a word.&lt;/p&gt;

&lt;p&gt;In my game, as you will see in the next blog post, I have a small data set — less than 200. I can check if a guess is within this array, but I don’t think it fits that game, so I decided to not implement it.&lt;/p&gt;

&lt;p&gt;With that said, I did want to shake my rows! This is the first turn I took from the original game — I am shaking the current guess row when the user is trying to enter a guess they already made today! In the original game, they let you waste a guess on a duplicate word — so watch out for that!&lt;/p&gt;

&lt;p&gt;Well, how do we shake the row?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2F7kbcfu541qi520xqgqbg.gif" alt="Shake shake shake" width="472" height="338"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shake shake shake&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For this, we will need CSS animation. This is not the first time we have used animations in the project. We bounce tiles when the user types in characters, and flip tiles to reveal the characters’ colors! Let’s start by creating the shaking animation in the CSS:&lt;/p&gt;

&lt;p&gt;All animations have the same structure: @keyframes  and division to the percentage of progress. In our case, we want back-and-forth of left and right (lines 2–11 below). Then, we create a new class that will use it (lines 13–15):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now we need to give the appropriate row div this class name. Which is the appropriate row? The shaking will be of the row that is currently in typing. So, we’ll modify Row.js:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the code above, we pass a property of shaky to the row component (line 1). The current row will shake (line 8 above) only if the shaky bool is set to true (line 2). Where do we set that value? At the same place we “find out” the user already tried this word — in useWordle hook!&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The fun part is that this code was already in the base code, so no extra work was needed from us, just to add the setShaky() function in the right places:&lt;/p&gt;

&lt;p&gt;We set shaky to true in the block where we check if the history includes this word (line 8). We also need to set shaky back to false so it will stop shaking after the first character deletion (line 15).&lt;/p&gt;

&lt;p&gt;Lastly, as line number 2 implies, we need to add the shaky variable to Wordle.js:&lt;br&gt;
const [shaky, setShaky] = useState(false)&lt;/p&gt;

&lt;p&gt;Note that this time we pass the setting function, not the variable:&lt;/p&gt;

&lt;p&gt;const useWordle = (…, setShaky)&lt;/p&gt;

&lt;p&gt;Look at it shake! Lastly, we need to add a tooltip: We should notify the user why the guess didn’t enter. We already have a tooltip in the code so this will be easy for you to implement by yourselves.&lt;/p&gt;

&lt;p&gt;With this done, we successfully finished our list of features!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Ffxkx69eeck13hk7sj8vd.gif" alt="Shaky row in action!" width="800" height="1422"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shaky row in action!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Final Touches
&lt;/h2&gt;

&lt;p&gt;Now that we have a cool game, there are a few steps to make it even cooler!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactoring the code to use only storageData&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Remember earlier, when we noticed a disgusting amount of duplicate code in useWordle.js? We will fix it now! Start cautiously by going to Wordle.js and adding variables instead of returning them from the useWordle hook and see if anything breaks.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Once that looks ok, go to useWordle.js and remove those variables and their setters. You will need to use&lt;br&gt;
storageData. instead of those variables in some places, but overall this is a simple change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phone screen capability&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Make sure the website view is neat from the phone as well. Check both Android and iPhone, because they don’t present the same way. A useful trick I learned in this project is to set font size or div width with this structure: min(9vw,40px);. Pixels are fixed, but vw is viewport width, which works like a percentage. Keeping the pixel option is good because vw gets too big on the laptop view, and this sizing structure keeps it proportional.&lt;/p&gt;

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

&lt;p&gt;The whole goal of this project is to wink at the original Wordle. What better way to do so, than gather similar fonts as well? My search showed the original Wordle uses a special New York Times font (that I assume is out of reach), so I used &lt;a href="https://fonts.google.com/specimen/Abhaya+Libre" rel="noopener noreferrer"&gt;Abhaya Libre&lt;/a&gt; and &lt;a href="https://fonts.google.com/specimen/Abril+Fatface" rel="noopener noreferrer"&gt;AbrilFatface&lt;/a&gt;, and I am pleased with the result :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More Refactoring&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;At first glance, yes — why bother if the user doesn’t see the code? But, refactoring has two main advantages: First, optimizing the code and using better practices, and names. This will minimize things that could cause you to make oopsies. Second, it will sharpen your React skills. You’ll learn additional (better) ways to produce the same output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Only Feature I Couldn’t Figure Out&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;In the original game, when the user already played today and clicks the See stats button from the landing page, there is a small delay before showing the modal. This delay exposes the tiles beautifully flipping with the previous guesses. The code, as it is now, has flipping tiles animation, but I didn’t manage to delay the modal.&lt;br&gt;
If you decide to work on this feature, please remember: The modal should be delayed &lt;strong&gt;only&lt;/strong&gt; in two situations: When the user is winning, which is when we have the line setTimeout(() =&amp;gt; setShowProgressModal(true), 2000), or when the user arrives from the landing page. There should be no delay when the user clicks the header button!&lt;/p&gt;

&lt;p&gt;Wow, what a journey! In this blog post, we worked on the share button, set different views based on the user’s game state, and shook some rows!&lt;/p&gt;

&lt;p&gt;In the next blog post, we will transfer our game from a boring 5-letter word to anything you want! Mine is Taylor Swift songs :D&lt;/p&gt;

&lt;p&gt;Exciting news to all the swifties out there — my Wordlestruck game will launch at the end of this blog post series!&lt;/p&gt;

&lt;p&gt;See you soon!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cupofcode.blog/" rel="noopener noreferrer"&gt;https://cupofcode.blog/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :) Thank you for your support!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Wordle in React: Picking Up Where The Others Left Off!</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sat, 27 Jan 2024 17:09:51 +0000</pubDate>
      <link>https://forem.com/cupofcode/wordle-in-react-picking-up-where-the-others-left-off-4oga</link>
      <guid>https://forem.com/cupofcode/wordle-in-react-picking-up-where-the-others-left-off-4oga</guid>
      <description>&lt;h3&gt;
  
  
  Learn how to create the guess distribution, the landing page, and all the cool features that are in the original Wordle game!
&lt;/h3&gt;

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

&lt;p&gt;I’ve recently decided I would like to improve my React skills. Sure, I’ve followed &lt;a href="https://react.dev/learn/tutorial-tic-tac-toe" rel="noopener noreferrer"&gt;the official React tic-tac-toe tutorial&lt;/a&gt; before, but things still didn’t click for me. I knew the solution was to create a fun project, so I found a &lt;a href="https://www.nytimes.com/games/wordle/index.html" rel="noopener noreferrer"&gt;Wordle&lt;/a&gt; tutorial! It’s called &lt;a href="https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX" rel="noopener noreferrer"&gt;&lt;strong&gt;Make a Wordle Clone with React&lt;/strong&gt; by Net Ninja&lt;/a&gt;, and that was a great start.&lt;/p&gt;

&lt;p&gt;Unfortunately, this project wasn’t close &lt;em&gt;enough&lt;/em&gt; to the real game. Don’t get me wrong, it had the main functionality, which is very cool to implement, but I was passionate about adding the missing features that appear in the original Wordle!&lt;/p&gt;

&lt;p&gt;In this tutorial, we will grab the Wordle-clone project that many people on YouTube created, and take it to the finish line! In a future blog post, I will show you how to theme your game with more exotic topics than ‘5 letter words’ :D&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fbtp68l3uospqhb1fqdsb.png" alt="*So many React Wordle tutorials and they all stop at the same stage*" width="800" height="448"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;So many React Wordle tutorials and they all stop at the same stage&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To make things clear, I will refer to the code I produced by following &lt;em&gt;Net Ninja&lt;/em&gt;’s tutorial as &lt;em&gt;“the base code”&lt;/em&gt;. &lt;a href="https://github.com/iamshaunjp/React-Wordle/tree/lesson-16" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is a link, in case you want to check it out.&lt;/p&gt;

&lt;p&gt;The agenda for this blog post is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Useful React tips for beginners&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quick overview of the base code we are starting with&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The virtual keyboard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The post-game modal&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The landing page&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculating game index&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shall we begin?&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%2F82tl7ruzpznmfhw4nn66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82tl7ruzpznmfhw4nn66.png" alt="Yay!" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Useful React tips for beginners
&lt;/h2&gt;

&lt;p&gt;I know it all looks overwhelming when you are just starting with React, so let me make things a bit clearer!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;There are two Reacts out there: &lt;em&gt;React Native&lt;/em&gt; and &lt;em&gt;React/ReactJs&lt;/em&gt;. ReactJS is for making front-end websites, and this is the one we are using today.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating a new project: Super simple, follow &lt;a href="https://legacy.reactjs.org/docs/create-a-new-react-app.html#create-react-app" rel="noopener noreferrer"&gt;this&lt;/a&gt; paragraph and you are good to go. If everything goes well, your browser will open a new tab for &lt;code&gt;localhost:3000&lt;/code&gt; with the default starting page:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fclzo64zdqblzgmmsentr.png" alt="*The starting page*" width="800" height="894"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;The starting page&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Running the app locally: You can either use &lt;code&gt;npm start&lt;/code&gt; in the command line or hover above &lt;code&gt;start&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; and then click &lt;code&gt;Run Script&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fgpf3ze9dyk31fg3z3q7r.png" alt="Running the app locally" width="800" height="390"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Running the app locally&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You don’t need to restart when you want to see your code changes, it will update automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You should try refreshing the browser page when you are fixing an error — because those sometimes stay on the screen even when the issue is fixed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;There is a useful VS code extension called &lt;strong&gt;&lt;em&gt;ES7+ React/Redux/React-Native snippets&lt;/em&gt;&lt;/strong&gt;. With that installed, you can create a new component file, type in &lt;code&gt;rfc&lt;/code&gt; and it will auto-complete the structure of the component.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are some solutions for problems you will probably encounter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Double rendering: When you start printing to the console you will quickly notice that everything is rendering twice. This is not a bug in your code! This is a React thing. This does not happen in Production, just locally in the development environment. You can read more about it &lt;a href="https://stackoverflow.com/questions/48846289/why-is-my-react-component-is-rendering-twice" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Syntax tips and tricks:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The error &lt;em&gt;“JSX expressions must have one parent element”&lt;/em&gt; means you need to wrap your HTML elements with empty tags: &lt;code&gt;&amp;lt;&amp;gt;&amp;lt;your code&amp;gt;&amp;lt;/&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The syntax for mapping over a JS array to produce HTML elements is as follows:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;The syntax for styling inside the HTML: &lt;code&gt;&amp;lt;h1 style={{paddingLeft: “70px”}}&amp;gt;&lt;/code&gt; . Notice the double curly brackets, the camelCase, and the stringifying of the value only (but not the key).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Updates via the &lt;strong&gt;UseState&lt;/strong&gt; hook do not reflect immediately, which means setting a value and then immediately getting it, won’t work as you intended. You can read more about it &lt;a href="https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setMovies(result);
console.log(movies) // movies!==result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Deploying to an App Deployment Platform
&lt;/h2&gt;

&lt;p&gt;Excitement to share your final product is an appropriate outcome of following this tutorial, and Netlify is the way to do so. &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; is a website that hosts your project for free!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uploading to &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can watch the full tutorial &lt;a href="https://youtu.be/ZW1Q8rO18ZI?si=Y0J15gJAI5k00Dph&amp;amp;t=173" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but the TLDR is:&lt;br&gt;
&lt;code&gt;build&lt;/code&gt; the project (you can find it in the package.json file, just below &lt;code&gt;start&lt;/code&gt;). This will create a build directory with all the needed files. Then, sign up to Netlify, and upload the &lt;code&gt;build&lt;/code&gt; directory. It’s that simple!&lt;/p&gt;

&lt;p&gt;I recommend you give it a try now and deploy the project you have as it is currently, before adding code. This way, you’d know if the blank page you see is a code issue or a deployment issue due to it being your first time using the platform.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Speaking of a blank page, let me introduce you to the &lt;em&gt;white screen of death&lt;/em&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This refers to a situation where our React application appears to have crashed and only displays a blank white screen. This is very frustrating, especially when the app works fine on the laptop but not on the phone (and how do you debug there?!).&lt;/p&gt;

&lt;p&gt;This is usually caused by JS code such as Date formatting, regex expressions, etc. I quickly learned that Chrome and Android phones are more forgiving than iPhones and some browsers on Mac (Safari, Firefox) so I highly recommend checking your website there periodically. If you see an error when opening the website on an iPhone, you will probably encounter the issue in Firefox on the Mac as well, where you have developer tools and can see the error.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The definition of done of any feature should include testing the iPhone view. Checking it via the developer tools’ responsive view isn’t enough, because it doesn’t give an accurate display. You’ll see examples of that later in the blog post.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now you are ready to start!&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%2Fwq3mkx3d3l67x92gu57s.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%2Fwq3mkx3d3l67x92gu57s.jpg" alt="meme" width="702" height="395"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Base Code
&lt;/h2&gt;

&lt;p&gt;No need to show here how to do those steps because &lt;a href="https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX" rel="noopener noreferrer"&gt;the YouTube tutorial&lt;/a&gt; does it very well. I’ll detail the state of the project post the tutorial, to connect you to the rest of the blog post.&lt;/p&gt;

&lt;p&gt;The base code from &lt;em&gt;Net Ninja&lt;/em&gt;’s GitHub can be found &lt;a href="https://github.com/iamshaunjp/React-Wordle/tree/lesson-16" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You can either fork his code and come join me now, or revisit this blog post after following his tutorial!&lt;/p&gt;
&lt;h3&gt;
  
  
  If you want to pull the base code:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pull the code from Net Ninja&lt;/strong&gt;:
a. Open a terminal in your projects directory.
b. Run the following commands one by one:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/iamshaunjp/React-Wordle.git
cd .\React-Wordle\
git pull origin lesson-16 --allow-unrelated-histories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;You might need to resolve conflicts in &lt;code&gt;README.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;c. Edit the &lt;code&gt;README.md&lt;/code&gt; to be yours, and don't forget to credit &lt;em&gt;Net Ninja&lt;/em&gt; for the base code!&lt;/p&gt;

&lt;p&gt;d. Now you have the code locally!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make sure it runs smoothly&lt;/strong&gt;:
a. Open your React-Wordle project in your IDE and open a new terminal
b. Install dependencies by running the following commands one by one:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i
npm i json-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;c. Run the json-server:&lt;br&gt;
 i. Open a new terminal&lt;br&gt;
ii. Run the command &lt;code&gt;json-server .\data\db.json --port 3001&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;d. Open &lt;code&gt;package.json&lt;/code&gt;, hover above "build" and click "Run Script"&lt;/p&gt;

&lt;p&gt;e. A new tab will open and you’ll see the game!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connect the project to your GitHub repository&lt;/strong&gt;:
a. Create a new repo at GitHub.
b. Open the terminal in your project directory.
c. Run the following commands one by one:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote set-url origin &amp;lt;URL_TO_GITHUB_REPO&amp;gt;
git add .
git commit -m "Starting with NetNinja's base code"
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;d. Now your code is in your GitHub repository!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Ffnjwslug79dv0t4lnckb.png" alt="Run the base code locally" width="800" height="605"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Run the base code locally&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Overview of the base code
&lt;/h3&gt;

&lt;p&gt;When you run the code you will see the page with the grid and the keyboard. The keyboard is not clickable and is here to show you all the letters and their colors. Above the grid there is the solution for this round, and the current guess the user is typing (for testing purposes).&lt;/p&gt;

&lt;p&gt;Every time the user types a guess and presses enter, the tiles flip and color with green, yellow, or grey. The game ends when the user guesses the solution word or when the user does not manage to guess the solution word within 6 tries. Then, a modal pops up with a winning/losing message, the solution word, and the number of guesses it took.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt; that every time you refresh the page, there will be a different solution word. The original Wordle game gives one word per day, and we will be coding this feature in today’s blog post! :D&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2F4rmt35cejt3r174oz0f8.png" alt="*Screenshots from [Net Ninja’s YouTube tutorial]*(https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX)" width="800" height="513"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Screenshots from &lt;a href="https://www.youtube.com/watch?v=ZSWl5UwhHcs&amp;amp;list=PL4cUxeGkcC9gXdVXVJBmHpSI7zCEcjLUX" rel="noopener noreferrer"&gt;Net Ninja’s YouTube tutorial&lt;/a&gt;&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My favorite part was the flipping tiles — I’ve never made CSS animations before — and now I know how! :D&lt;/p&gt;
&lt;h3&gt;
  
  
  This won’t work on Netlify!
&lt;/h3&gt;

&lt;p&gt;If you try to deploy the base code as it is now in &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; &lt;strong&gt;it won’t run&lt;/strong&gt;! This is because the base code is pulling the solution words from the JSON file by running &lt;code&gt;json-server&lt;/code&gt; in a separate terminal, which simulates a RESTful API. Then, the line &lt;code&gt;fetch(‘http://localhost:3001/solutions')&lt;/code&gt; breaks in Netlify.&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;json-server&lt;/code&gt; package doesn’t work in Netlify because Netlify can only host static pages (with no servers running on parallel terminals).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: Uninstall this package (&lt;code&gt;npm uninstall json-server&lt;/code&gt;) and modify the code to simply call the local file: &lt;code&gt;import solutions from './data/db.json';&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Virtual Keyboard
&lt;/h2&gt;

&lt;p&gt;The keyboard in the original game has two roles: Visual and functional.&lt;/p&gt;

&lt;p&gt;First, show the color of the letters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Letters you haven’t tried yet — light grey&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Letters you guessed correctly in the right place — green&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Letters you guessed that are in the solution word but not in the right place — yellow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Letters you have tried but do not appear in the word at all — dark grey&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second purpose of the keyboard is to populate the grid tiles. The user can click the virtual keyboard and it will function the same way as a regular keyboard, which is useful for users on mobile and Padlets.&lt;/p&gt;

&lt;p&gt;The keyboard we start with from the base code does not have any functionality, and it doesn’t look like a regular keyboard, so we are now going to fix that!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sp4prywc45zq5p06d73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sp4prywc45zq5p06d73.png" alt="keyboard before and after" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Making It Look Like A Real Keyboard
&lt;/h3&gt;

&lt;p&gt;First thing first — the keyboard is in lowercase letters! The original Wordle’s keyboard (and any keyboard in the world) has uppercase letters, so let’s change that!&lt;/p&gt;

&lt;p&gt;In the base code, the keyboard is created using a JSON object called letters:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "letters": [
    {"key": "a"},
    {"key": "b"},
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then the Keypad component fetches that object:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
We can simplify this! Remove it altogether and create an array of letters inside the component. No need for fetching and no need for useEffect hook! Tip: Don’t forget to order them in the &lt;a href="https://en.wikipedia.org/wiki/QWERTY" rel="noopener noreferrer"&gt;qwerty keyboard layout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that even though it’s tempting, you shouldn’t change the letters to uppercase. The user is typing in lowercase (like the letters you are reading now), and the function will compare them to what would now be uppercase letters, and will always return a mismatch. So how do we get the uppercase visuals? With the CSS file!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.keypad &amp;gt; div {
  ...
  text-transform: uppercase;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you run your app now, you might see the keyboard’s keys spilling to the wrong places: P goes to the second row, and L goes to the third row, depending on the width of the window. I fixed that by dividing my array of letters into three.&lt;/p&gt;

&lt;p&gt;Now, to build the keyboard, I’m looping over the 3 arrays. This solution makes it very easy to add “Enter” and “Backspace” buttons: I just add them separately before and after the third loop!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Making the Virtual Keyboard Clickable
&lt;/h3&gt;

&lt;p&gt;The most straightforward approach here is to add “handleKeyup” function as the keys’ “onClick” event. Unfortunately, it didn’t work.&lt;/p&gt;

&lt;p&gt;I couldn’t get the virtual keyboard to give the “handleKeyup” function the right parameters to make it work (something about it being an event handler and this &lt;code&gt;{key}&lt;/code&gt; parameter). This caused me to use a workaround:&lt;/p&gt;

&lt;p&gt;I took the content of handleKeyup and wrapped it in a new function, one that doesn’t have a weird &lt;code&gt;{key}&lt;/code&gt; parameter. That way I had one source of truth to handling the user typings!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const useWordle = (solution) =&amp;gt; {
  ...
  const handleKeyup = ({ key }) =&amp;gt; {
    handleKeyupVirtual(key)
    return
  }
  ...
  return {
    ...
    handleKeyupVirtual
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To use this new function, we need to output it from the useWordle hook and pass it to the Keypad component:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function Wordle({...}) {
  const { ..., handleKeyupVirtual} = useWordle(...);
  ...
  &amp;lt;Keypad usedKeys={usedKeys} handleKeyupVirtual={handleKeyupVirtual} /&amp;gt;
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And then in Keypad.js:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
  ...
  &amp;lt;div key="Enter" style={{width: "15%", fontSize:"13px"}}
     onClick={() =&amp;gt; handleKeyupVirtual("Enter")}&amp;gt;Enter&amp;lt;/div&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Fixing a Tiny Bug
&lt;/h3&gt;

&lt;p&gt;When I opened the game on the iPhone I discovered a problem: The backspace character is very small, regardless of the font size.&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%2Fdcvsfoiamys9tlk5iluz.jpeg" 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%2Fdcvsfoiamys9tlk5iluz.jpeg" alt="keyboard bug" width="473" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How do we solve this? I checked how the original game did it: I opened the NY Times Wordle with the developer tools and saw they used an image instead of a character, so I did the same.&lt;/p&gt;

&lt;p&gt;After finding an appropriate image online and placing it on my keyboard, the image was stuck at the top of the key and I wasn’t able to resolve it with direct CSS modification: When I tried to modify the margin or the padding, it affected the entire row. It was not as easy as it initially looked!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2F136mlqrlfw43u4fiay8j.png" alt="*When I added a margin to the backspace image — it affected the entire keyboard row*" width="600" height="268"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;When I added a margin to the backspace image — it affected the entire keyboard row&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Eventually, I did find a trick on &lt;a href="https://stackoverflow.com/questions/7273338/how-to-vertically-align-an-image-inside-a-div" rel="noopener noreferrer"&gt;Stack overflow&lt;/a&gt;: Add the “no wrap” white-space property to the backspace div, and add inside it a helper span. Now it’s in a perfect size!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Touches
&lt;/h3&gt;

&lt;p&gt;The keyboard is functioning and it looks good, but there are a few more things we can do to make it perfect!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding delay to the key color change&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might have noticed that when you play the original game, the keyboard key color updates only after the grid row is colored. In our game at the moment, the keyboard updates quicker because of the tiles’ animation.&lt;/p&gt;

&lt;p&gt;I was looking into delaying components in react when I suddenly realized there must be an easier approach via CSS — and my hunch was right! All you need to do is go to the &lt;code&gt;.keypad &amp;gt; div.green&lt;/code&gt; (and yellow and grey) and add &lt;code&gt;transition-delay: 1s;&lt;/code&gt; — That’s it! :D&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Addressing CapsLock in handleKey&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At some point, I’ve noticed that if the CapsLock is on, it messes up the entered guess. To solve this, I added an if condition to catch that key, and ignore it (just return):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const useWordle = (solution) =&amp;gt; {
  ...
  const handleKeyupVirtual = ( key ) =&amp;gt; { 
    if (key === 'CapsLock') {
      return;
    }
    if(key === 'Enter'){
      ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Position the keyboard at the bottom of the page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the user is on the mobile, it’s more comfortable to have the keyboard lower on the screen (closer to the thumbs). The keyboard is currently right below the grid, which is closer to the center.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fn0gbde3ap04hue0fnipc.png" alt="*Positioning the keyboard at the bottom is better for mobile users*" width="800" height="632"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Positioning the keyboard at the bottom is better for mobile users&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With that said, I like that the keyboard is just below the grid when it’s the desktop view. This means we need different styles for different screen sizes!&lt;/p&gt;

&lt;p&gt;We start by adding a &lt;code&gt;@media&lt;/code&gt; rule with the condition of &lt;strong&gt;max-width: 700px&lt;/strong&gt;, so it would affect only a narrow screen view. Inside that, we add the second version of the keypad style.&lt;/p&gt;

&lt;p&gt;We need two modifications to make it work: First is adding to the keypad &lt;code&gt;position: absolute;&lt;/code&gt; along with bottom, right, and left = 0. Then, we need to add to the body’s style &lt;code&gt;position: relative;&lt;/code&gt; and set the height to be &lt;code&gt;90vh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you set it to 100vh, it will look beautiful on your screen, in any size, but will require scrolling on the iPhone (I encourage you to check for yourself).&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Now, after all these modifications, we have a beautiful functioning keyboard:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fglkrdierf7xabz1y18vl.png" alt="The new keyboard — both pretty and smart!" width="762" height="256"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;The new keyboard — both pretty and smart!&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Post Game Modal
&lt;/h2&gt;

&lt;p&gt;When the game is over, pops up a modal with some interesting information.&lt;/p&gt;

&lt;p&gt;In the original game, there are statistics including the number of games played and the percentage of games won.&lt;/p&gt;

&lt;p&gt;Below that, you can find past games’ guess distribution: How many times did the user guess the right word in how many tries.&lt;/p&gt;

&lt;p&gt;Lastly, there is a share button that produces a string of squares, representing the game, that the user can share with their friends. We will create all those cool features!&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%2F9uimuept3om3ia2hwtw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uimuept3om3ia2hwtw6.png" alt="before and after" width="702" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Close Button
&lt;/h3&gt;

&lt;p&gt;I’ve started with the thing that annoyed me most: I couldn’t close the modal. I had to add an exit button.&lt;/p&gt;

&lt;p&gt;This is super easy to implement in pure HTML and JS but I wasn’t sure how to do it correctly in React. According to the base code, the modal shows up with the boolean &lt;code&gt;showModal&lt;/code&gt;, and its value is set in the “Wordle” component:&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Close Button
&lt;/h3&gt;

&lt;p&gt;I’ve started with the thing that annoyed me most: I couldn’t close the modal. I had to add an exit button. This is super easy to implement in pure HTML and JS but I wasn’t sure how to do it correctly in React. According to the base code, the modal shows up with the boolean &lt;code&gt;showModal&lt;/code&gt; and its value is set in the “Wordle” component:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Even if I add an X button, how do I change the value of that &lt;code&gt;showModal&lt;/code&gt; boolean from &lt;em&gt;within&lt;/em&gt; the Modal component? The trick is to pass &lt;code&gt;setShowModal&lt;/code&gt; as a parameter to the component!&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Fun fact: &lt;code&gt;&amp;amp;times;&lt;/code&gt; is a multiplication sign in HTML. It looks a bit different than “x”.

&lt;p&gt;&lt;strong&gt;Now the grid page is accessible post-game&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s important to mention that adding the functionality to close the modal means the user can see the grid of guesses after the game is over. This is fine, but we need to make sure he can’t type in any more guesses, both on the physical and virtual keyboards.&lt;/p&gt;

&lt;p&gt;For the physical keyboard, we can simply add &lt;code&gt;else&lt;/code&gt; to the “if-user-won” inside the useEffect, and only there add the keyup listener.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Please note that in the base code, there are different modal versions for winning and losing (and therefore different &lt;code&gt;if&lt;/code&gt; conditions), but I merged them — so in my code, it’s one if condition: if the guess &lt;strong&gt;is correct&lt;/strong&gt; or if it’s not correct but we &lt;strong&gt;reached the 6th try&lt;/strong&gt; (&lt;code&gt;if (isCorrect || turn &amp;gt; 5)&lt;/code&gt;)

&lt;p&gt;For the virtual keyboard, we want to override the clickability of the keys on that last turn. Let’s see it in the code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
We start by passing an argument notifying the keypad component that it should lock the keys. Considering we want to indicate the game is over, we can use the same boolean we use in the if-the-game-ended statement: &lt;code&gt;isLocked = (isCorrect || turn &amp;gt; 5)&lt;/code&gt;. Now, the trick is to use &lt;code&gt;pointerEvents&lt;/code&gt; property: We set it as “none” or “auto” based on the value of &lt;code&gt;isLocked&lt;/code&gt;. Lastly, we add it to the style of the keyboard’s keys (you should have a total of 5 occurrences: top, middle, and bottom rows, Enter, and Backspace).


&lt;h2&gt;
  
  
  Guess Distribution
&lt;/h2&gt;

&lt;p&gt;Now we can move on to the coolest feature of the modal: Let’s show the user their statistics! In the total games they played, what was their guess distribution? This is divided into 2 parts: collecting the statistics, and presenting them.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Collect the&lt;/strong&gt; statistics
&lt;/h3&gt;

&lt;p&gt;The base code doesn’t save how many guesses the player used, so we need to add that ourselves… but how?&lt;/p&gt;

&lt;p&gt;We want this information to be saved even when the user closes the website. An easy way to do so is to use &lt;strong&gt;local storage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Local storage’s stored data is saved across browser sessions, with no expiration time. So, if there is no statistics object at all — create one. Then, read the previous statistics, update them with this game’s result, and save them in the local storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important tip!&lt;/strong&gt; When coding with local storage, you need the ability to restart it, for testing purposes. I have this line of code commented out and easy to reach in App.js: &lt;code&gt;//localStorage.removeItem(“wordlestruckStatistics”);&lt;/code&gt;. Comment it in, refresh, comment it out, refresh — and you get a clean slate.&lt;/p&gt;

&lt;p&gt;Let’s write some code!&lt;/p&gt;

&lt;p&gt;We will create an array of length 7, to represent the possible statistics: User guessed the solution after 1,2,3,4,5,6 tries, or they lost. 0 will represent the loss.&lt;/p&gt;

&lt;p&gt;The tricky part is that when the user loses, the turn is set to 6 (because it was their 6th try), so we need to adjust the index based on whether the user was correct: &lt;code&gt;let index = isCorrect ? turn : 0;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now — where should the code sit? It can sit in the Modal component, or in the Wordle component. It’s best to update the statistics when the game is finished, so we will add our new code in the “If the game finished” condition in the Wordle component.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Present the statistics
&lt;/h3&gt;

&lt;p&gt;I encourage you to create a new React component dedicated to this section (&lt;code&gt;&amp;lt; Statistics /&amp;gt;&lt;/code&gt;) and call it from the Modal component.&lt;/p&gt;

&lt;p&gt;First, you need to pull the data from localStorage. Then, save the number of total games in a variable. I present the data using a table element: The left column is the possible number of guesses (0–6), and the right column is the grey bar charts representing the distribution.&lt;/p&gt;

&lt;p&gt;Now, those aren’t just any boring bar charts! In the original game, the distribution is written inside the bar, and the bar’s size depends on the distribution amount (but 0 isn’t empty). Also, the bar representing the number of guesses it took the user for the current game is colored green, not grey.&lt;/p&gt;

&lt;p&gt;How do we achieve all that? For our bar charts, we’ll be using divs, so it’s a no-brainer to write in them. For the bar length, here is the math I did: &lt;code&gt;Math.floor((stat / totalGames) * 80) + 8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I divided stat-for-specific-guess-number by the total number of games to reach a proportional size. I multiplied by 80 because I didn’t want the biggest size to reach 100% width (but this is my personal preference). Lastly, I added 8 because I set the size of the 0 distribution to be 4, and I wanted 1 to be visibly bigger than that, even if there were many games played.&lt;/p&gt;

&lt;p&gt;What about the green color? That property depends only on the turn number: &lt;code&gt;let barColor = (index === turn) ? "green" : "rgb(107, 107, 109)";&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Full code for the guess distribution section:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Screenshot:

&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%2Fmxsg071x4mjrvpxjuv2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxsg071x4mjrvpxjuv2i.png" alt="screenshot" width="698" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above you can see I also presented information on total games played and % of wins. After completing the guess distribution, this part will be easy for you to add by yourselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maybe we shouldn’t show the user his losses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Later in the development, I decided I didn’t want to present the number of losses. This can be achieved by wrapping the code inside the &lt;code&gt;map&lt;/code&gt; in &lt;code&gt;if(index!==0)&lt;/code&gt;. Not the prettiest but the most minor change. If you do insist on prettier, there is another solution: Create a copy of &lt;code&gt;objStatistics&lt;/code&gt; and &lt;code&gt;.shift()&lt;/code&gt; it — this will remove the first item. There is no critical harm in shifting the original array, but it’s not a good practice because it can confuse the human eye reading this code. &lt;code&gt;objStatisticsWithout0&lt;/code&gt; is not confusing anyone :)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// to remove first element - number of loses.
let objStatisticsWithout0 = objStatistics;
objStatisticsWithout0.shift();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you choose to go this route, you’ll need another index variable: We shift our array, which means &lt;code&gt;array[turn]&lt;/code&gt; does not match the guess distribution in &lt;code&gt;turn&lt;/code&gt;. I called mine &lt;code&gt;guessDistIndex&lt;/code&gt; and set it to be &lt;strong&gt;index +1&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Lastly, before we continue, I found it useful to make the modal scrollable. You can easily achieve that by adding in &lt;code&gt;index.css&lt;/code&gt;:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.modal {
  ...
  overflow-x: hidden;
  overflow-y: auto;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;This is it for now. We will come back to the Modal after we go over the features that the rest of it depends on :D&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;Ok, remember when I said that in a future blog post, I’ll show you how to give your Wordle a cool theme? In the next section, I expose the theme of my Wordle because I can’t hide it anymore — it will appear in the screenshots.&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%2Fyfpjft1cpqvfyj3w4001.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyfpjft1cpqvfyj3w4001.png" alt="meme2" width="759" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Wordle game is about Taylor Swift songs! The juicy part of a themed Wordle is creating the data set and adjusting the functionality to also work with solutions longer than 5 characters, and solutions containing more than one word. I will touch on those cool features in a future blog post, so stay tuned!&lt;/p&gt;


&lt;h2&gt;
  
  
  The Landing Page
&lt;/h2&gt;

&lt;p&gt;All the Wordle clone projects start the game with the main game page (the one with the grid and the keyboard), but the true Wordlers (&lt;em&gt;“Hi Wordler”&lt;/em&gt;) among you know that there is a grey landing page with “How to play” and “Play” buttons. This is what we will work on in this section!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fiz3tuqe736lsh73mjye0.png" alt="*The landing page*" width="729" height="681"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;The landing page&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The structure of the page is pretty clear: Image, title, text, 2 buttons, more text. The tricky part is clicking the &lt;strong&gt;&lt;em&gt;Play&lt;/em&gt;&lt;/strong&gt; button and “moving between pages” while staying on the same page with the same URL, without routing!&lt;/p&gt;

&lt;p&gt;We’ll start by creating a new component, which I called &lt;code&gt;&amp;lt;Welcome /&amp;gt;&lt;/code&gt;, and we add all the needed data there. You can present the date in this format by using:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var today = new Date();
var displayDate = today.toLocaleDateString("en-US", options);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Calculating the index, on the other hand, is a tricky part which I will touch on later! Keep it hard-coded for now.&lt;/p&gt;

&lt;p&gt;Adding this new component requires some refactoring because it sits between &lt;strong&gt;App.js&lt;/strong&gt; and &lt;strong&gt;Wordle.js&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;App.js&lt;/strong&gt; used to call &lt;strong&gt;Wordle.js&lt;/strong&gt;, but now it will call &lt;strong&gt;Welcome.js&lt;/strong&gt;, and &lt;strong&gt;Welcome.js&lt;/strong&gt; will call &lt;strong&gt;Wordle.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, we want the page to show the Wordle component if the button was clicked. This means the landing page content disappears and the Wordle content appears. We achieve that by using React’s &lt;a href="https://react.dev/learn/updating-objects-in-state" rel="noopener noreferrer"&gt;useState&lt;/a&gt;. That way we trigger a re-render to &lt;em&gt;replace&lt;/em&gt; a value:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [showWordle, setShowWordle] = useState(false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So, on button click we set &lt;code&gt;showWordle&lt;/code&gt; to be true, and we wrap the corresponding code sections so that they are dependent on the boolean value!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{!showWordle &amp;amp;&amp;amp; (&amp;lt;landing page code&amp;gt;)} {showWordle &amp;amp;&amp;amp; (&amp;lt;Wordle component&amp;gt;)}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let’s see it in the Welcome component:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  The Instructions Modal
&lt;/h3&gt;

&lt;p&gt;It’s time to address the second button on the landing page. The modal itself is nothing we haven’t already seen, but the tricky part is showing it above the grid page!&lt;/p&gt;

&lt;p&gt;In the original game, clicking the &lt;strong&gt;&lt;em&gt;how to play&lt;/em&gt;&lt;/strong&gt; button leads you to the same page as the &lt;strong&gt;&lt;em&gt;play&lt;/em&gt;&lt;/strong&gt; button, just with the extra instructions modal. Once you click the &lt;strong&gt;&lt;em&gt;X&lt;/em&gt;&lt;/strong&gt; button you’ll see the grid and the keyboard.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&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%2Fhwtv86bqvdzyuygu504o.png" alt="*Wordlestruck’s instructions modal*" width="800" height="792"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Wordlestruck’s instructions modal&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So, you create a new component (&lt;code&gt;&amp;lt;Instructions /&amp;gt;&lt;/code&gt;) and put your code there. Where should you call it from? The structure will be similar to the &lt;strong&gt;&lt;em&gt;landing page/wordle page&lt;/em&gt;&lt;/strong&gt; structure, but from inside the wordle component!&lt;/p&gt;

&lt;p&gt;We established earlier that both buttons will lead to the grid page, it’s just a question of whether or not the instructions modal will be shown on top. To achieve that, when we call the wordle component, we will pass a boolean parameter for the instructions page: &lt;code&gt;{showWordle &amp;amp;&amp;amp; &amp;lt;Wordle solution={solution} showInstructions={showInstructions} /&amp;gt;}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s a good start, but it’s not enough. We also need to update the condition of showing the landing page vs showing the wordle component to take into consideration that new boolean:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that the initial state is to see the landing page, which means not showing the Wordle component, and not showing the instructions modal on top of the Wordle component. Then, if one of those booleans is set to true, we want to remove the landing page code and show the Wordle component.

&lt;p&gt;So, we finished with the changes in the Welcome component, and we passed an additional parameter to the Wordle component. Let’s adjust the code to consider that new parameter!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside the Wordle component:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This time, different from the landing page logic, we don’t need to attach the Wordle code (grid, keyboard) to the &lt;code&gt;!showInstructionsModal&lt;/code&gt; condition because we want it to exist underneath the instructions modal. That way, when we close the modal we will see the game!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
I didn’t show any code for the Instructions modal, because it’s easy to implement considering it’s not our first modal in the project. You can do it! :D


&lt;h2&gt;
  
  
  Calculating the Game Number (Index)
&lt;/h2&gt;

&lt;p&gt;At first glance, it may seem like the index is a tiny feature that appears only once on the landing page. In reality, this is a crucial part of this game, and it will be used throughout the whole game flow!&lt;/p&gt;

&lt;p&gt;The index is accessed on the landing page, in the data set (to pull the word of the day), in the share button, and in various components to indicate if the user has played today or not. I know, most of those we didn’t do yet — which is why we need the index at this point!&lt;/p&gt;

&lt;p&gt;When I started planning this function, I got overwhelmed: Every month has a different number of days, some years are leap years, and it even gets more complicated — &lt;strong&gt;Date.now()&lt;/strong&gt; function returns the number of &lt;em&gt;milliseconds&lt;/em&gt; since January 1, 1970, but there is no function to calculate the number of milliseconds since January 1, 1970, to another date that is not &lt;strong&gt;now&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;At this point, I used the successful method “drop it for now and come back to it later”, and it worked! I googled it, understood all the parts (I can’t find the exact link but it was from &lt;a href="https://www.geeksforgeeks.org/" rel="noopener noreferrer"&gt;Geeks for Geeks&lt;/a&gt;), and was determined to write it myself!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to calculate the number of days between two dates?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First of all, the trick is not to calculate days &lt;em&gt;between&lt;/em&gt; two dates, but to calculate the number of days in each date (# of days in 2023 years, # of days in 3 months, etc) and subtract the two!&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that when you calculate years and months, you don’t use the direct value, but subtract 1: If the date is March 13th, 2023, we don’t have all days of March, nor all days of 2023. This is why you see in the code above &lt;code&gt;year-1&lt;/code&gt; and &lt;code&gt;month-1&lt;/code&gt;. The for loop that is calculating full months in days is running from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;month-1&lt;/code&gt; (excluding), which matches the &lt;code&gt;monthDays&lt;/code&gt; array: January’s amount of days is in cell number 0, not 1.
&lt;h3&gt;
  
  
  Addressing Leap Years
&lt;/h3&gt;

&lt;p&gt;The leap years aspect is not as complicated as it initially looks. A leap year is a year that has one additional day at the end of February. So, once you know how many years are leap years — that’s the amount of days you add.&lt;/p&gt;

&lt;p&gt;What makes a year a leap year? According to Wikipedia,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“This extra leap day occurs in each year that is an &lt;a href="https://en.wikipedia.org/wiki/Integer" rel="noopener noreferrer"&gt;integer&lt;/a&gt; multiple of 4 (except for years evenly divisible by 100, but not by 400)”.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In code, it simply means &lt;code&gt;if(year % 4 === 0 &amp;amp;&amp;amp; !(year % 100 === 0 &amp;amp;&amp;amp; year % 400 !== 0))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is how I wrote the function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Don’t forget that leap years have their extra day in February, so keep the last year out of this calculation, and add a day only if the month is after February (&lt;code&gt;month &amp;gt; 2&lt;/code&gt;).

&lt;p&gt;Just to make sure we tackled all possible edge cases, let’s think about what would happen on Feb 28th and Feb 29th.&lt;/p&gt;

&lt;p&gt;Feb 28th: &lt;code&gt;month&lt;/code&gt; is not &amp;gt;2 then leap year doesn’t impact our calculation.&lt;/p&gt;

&lt;p&gt;Feb 29th: &lt;code&gt;month&lt;/code&gt; is not &amp;gt;2 then leap year doesn’t impact our calculation in this part. When we add the day to the sum of total days, we will add 29, so that’s ok.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This caused a bug!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve discovered the hard way that Mac’s Safari and Firefox browsers do not agree to render the starting date in the current format. Chrome is more forgiving, which is why it took me a while to discover it.&lt;/p&gt;

&lt;p&gt;The acceptable format is &lt;code&gt;new Date(2023,11,8)&lt;/code&gt; — This means the month range is 0–11! (and only the month, not the other variables, I double-checked!)&lt;/p&gt;

&lt;p&gt;It’s more human-friendly to address the months as starting from 1 so it’s better to adjust the month variable (and leave yourself a comment as a reminder!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let startingDate = new Date(2023, 11, 8); // YYYY-(MM-1)-DD
...
let month = date.getMonth() - 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, you can optimize this by hard coding the start date. We set it only once, no need to calculate it again and again. I chose to keep the calculation because I haven’t launched my game yet, so the starting day is about to update.&lt;/p&gt;

&lt;p&gt;If you are passionate about optimizing the program, you can calculate the first 2023 years in days only once and save those in variables! I won’t do it here, so let’s call this your homework ;)&lt;/p&gt;

&lt;p&gt;Now that you have the game index calculated, you can modify the word pulling so that there will be only one word per day, based on the date!&lt;/p&gt;

&lt;p&gt;The base code has the line: &lt;code&gt;const randomSolution = solutions[Math.floor(Math.random() * solutions.length)]&lt;/code&gt;, which gives a new word every refresh of the page. If we change it to be &lt;code&gt;const todaysSolution = solutions[game_id]&lt;/code&gt;, where &lt;code&gt;game_id&lt;/code&gt; is the index calculated by our function, you could add the exciting sentence &lt;em&gt;“A new puzzle is released daily at midnight”&lt;/em&gt; to your Instructions modal! :D&lt;/p&gt;
&lt;h3&gt;
  
  
  Last but not least
&lt;/h3&gt;

&lt;p&gt;We need to take a closer look at the index ranges: Let’s say we have 200 solution words. The solutions data set indexes will range between 0–199, because of the convention that an array starts at 0. The game indexes (numbers) should range between 1-infinity. This means we have a mismatch both at the start and at the end of the index ranges. Also, if we want the game index to start at 1, we need to revisit our &lt;strong&gt;getGameIndexofToday()&lt;/strong&gt; function. We will fix it all in this section!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App.js:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is our code at the moment:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const game_id = getGameIndexofToday(); // 0-infinity, but we don't want "game no.0"
...
const todaysSolution = solutions[game_id]; // 0-199
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At some point, your game index will be higher than your dataset size. If you have 200 words in your data set, and it’s day 201, your app will crash. You can cover this case simply by adding &lt;code&gt;const solution_id = game_id % solutions.length;&lt;/code&gt; instead of using the value coming from the index calculation function directly.&lt;/p&gt;

&lt;p&gt;That is not enough. Once we fix &lt;code&gt;game_id&lt;/code&gt; to start at 1, the first solution word will be the second item in the array (&lt;code&gt;solutions[1]&lt;/code&gt;). We need to correct that! Therefore, we'll use &lt;code&gt;const solution_id = (game_id-1) % solutions.length;&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;num_of_days_from_date.js:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lastly, let’s look at &lt;strong&gt;getGameIndexofToday()&lt;/strong&gt; function. We built it so that we calculate amount of days in each date, and then subtract. What happens on the first day? The result will be 0, which is not a good starting index for our game (game number 0?). This happened because usually when we calculate something from start to finish, we exclude the end value. In our case, we need to include it. Let’s fix the code!&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Not only do I add +1 before I return the value, but I make sure to rename the function to make it explicit that it includes the end day.&lt;/p&gt;

&lt;p&gt;Now our indexes are matching!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const game_id = getGameIndexofToday(); // 1-infinity
...
const solution_id = game_id % solutions.length; // 1-200
const todaysSolution = solutions[solution_id]; // 1-200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This is a good place to close today’s tutorial. There are more features to add, but we’ll do them in the next blog post!&lt;/p&gt;

&lt;p&gt;Let’s give a quick reminder of what we created today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A beautiful and functional virtual keyboard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A cool statistics modal with the user’s guess distribution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An instructions modal&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A landing page with the real date and game index!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a lot! But a lot of fun as well. 😀&lt;/p&gt;

&lt;p&gt;In the next blog post, we will create the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The share functionality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Different screens for whether the user played today or not&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Header buttons for statistics and instructions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exciting news to all the swifties out there — my Wordlestruck game will launch at the end of this blog post series!&lt;/p&gt;




&lt;p&gt;Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in &lt;a href="https://ko-fi.com/cupofcode" rel="noopener noreferrer"&gt;my tipping jar&lt;/a&gt; will let me know :)&lt;/p&gt;

&lt;p&gt;Thank you for your support!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cupofcode.blog/" rel="noopener noreferrer"&gt;https://cupofcode.blog/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Weekend Coding: Daily Email Sending Through Code</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sun, 31 Dec 2023 14:53:17 +0000</pubDate>
      <link>https://forem.com/cupofcode/weekend-coding-daily-email-sending-through-code-igd</link>
      <guid>https://forem.com/cupofcode/weekend-coding-daily-email-sending-through-code-igd</guid>
      <description>&lt;h3&gt;
  
  
  A fun end-to-end project, from requirements to the final product
&lt;/h3&gt;

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

&lt;p&gt;A few weeks ago I went to visit my parents and we sat down for dinner. While we were catching up, my dad mentioned that every evening he goes on his computer, and sends my mom an email with several Excel files she has been updating that day, for backup. I almost choked on my food! Every evening? Manual backup? In 2023?!&lt;/p&gt;

&lt;p&gt;My first optimization idea was to swap Excel for Google Drive’s Sheet. This idea was declined because Excel’s user experience is so much better, that it is non-negotiable. Then my next suggestion was to set up a Google Drive syncing directory on the desktop. This idea was rejected as well because apparently, my dad had a resiliency threshold so high, that he did not want to put all his eggs in one basket: He wanted a multi-company backup, and he is achieving it by sending the files to two different email addresses, hosted by two different email providers.&lt;/p&gt;

&lt;p&gt;Now, let me be clear: I know that Google Drive is good enough for my mother’s precious spreadsheet files. This double-company backup approach is cracking a nut with a sledgehammer. With that said, this is what the customer wants, and I was excited to discover how to automate email sending, so we started laying down the requirements!&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%2Fborpwr9s7xly7swralvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fborpwr9s7xly7swralvb.png" width="406" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  (Initial) Requirements
&lt;/h2&gt;

&lt;p&gt;I was naive to think that we had everything covered at our first discussion. Later on, we realized that every little adjustment we decided to make along the way, resulted in additional, more specific, requirements, which I will cover later.&lt;/p&gt;

&lt;p&gt;Now, we know the final goal is to have a daily email sent from my father’s email address to my mother’s email address but to be exact, we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Daily &lt;em&gt;automatic&lt;/em&gt; email from &lt;em&gt;&lt;a href="mailto:X@hotmail.com"&gt;X@hotmail.com&lt;/a&gt;&lt;/em&gt; to &lt;em&gt;&lt;a href="mailto:Y@gmail.com"&gt;Y@gmail.com&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy &lt;em&gt;manual&lt;/em&gt; send upon request as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attaching &lt;strong&gt;multiple&lt;/strong&gt; Excel files, while the names of files may vary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, I had a few things to figure out, with which I could create the whole program:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Send an email through code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attach files to that email.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extract the code to .exe file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automate a daily send.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might wonder why step 3 is necessary. There are two reasons for that: First, I want the program to be user-friendly for non-developers (like my parents). Second, I want to hide the code because, as you will see soon, it contains a Gmail app password. I am assuming .exe files can be disassembled back to code, but at least this is an extra layer of protection.&lt;/p&gt;

&lt;p&gt;Luckily, I wasn’t the first one on the internet trying to code email sending, even with files attached! Nor I was the first to extract a .exe file from Python, and I was definitely not the first to want to automate an action on Windows. I will link useful videos at the end of the blog post. Now, let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Send an Email through Code
&lt;/h2&gt;

&lt;p&gt;I will admit that when thinking of automation and scripts, the first thing that came to mind was the &lt;a href="https://www.freecodecamp.org/news/command-line-for-beginners/#:~:text=A%20shell%20is%20a%20program,and%20uninstalled%20by%20the%20user." rel="noopener noreferrer"&gt;shell &lt;/a&gt;— using the command line. But a quick YouTube search showed Python is a more friendly way, and that is what I went with.&lt;/p&gt;

&lt;p&gt;Whatever coding language you are choosing, there is one thing you’ll need to use in order to send out emails: &lt;em&gt;SMTP&lt;/em&gt;, or &lt;em&gt;Simple Mail Transfer Protocol&lt;/em&gt;. This is how you send email messages between servers. If you will be using a &lt;em&gt;Hotmail&lt;/em&gt; email address, you will need to authenticate through the &lt;em&gt;Hotmail&lt;/em&gt; SMTP server.&lt;/p&gt;

&lt;p&gt;This is the first point where our initial plan shifted. Setting up an SMTP connection to Gmail, which is what I used, requires you to set up a &lt;em&gt;2-step verification&lt;/em&gt; in your account. A 2-step verification is verifying who you are with more than just your password (usually sending code through email/SMS). Higher security for your account is never a bad thing, but the fact that this is a &lt;em&gt;requirement&lt;/em&gt; shows that connecting to an SMTP server using your email address credentials is compromising your account.&lt;/p&gt;

&lt;p&gt;I want an email &lt;em&gt;sent&lt;/em&gt;, and I don’t mind from where (well, this will come back to bite us, stay tuned ;) ) — so I can create a new account, one I don’t mind compromising its security, which only purpose will be my new cool software!&lt;/p&gt;

&lt;p&gt;The way you set up your SMTP is by using &lt;strong&gt;app passwords&lt;/strong&gt;. An app password is a 16-digit passcode that gives a less secure app or device permission to access your Account. &lt;a href="https://support.google.com/accounts/answer/185833?hl=en" rel="noopener noreferrer"&gt;Here&lt;/a&gt; are instructions for setting up an app password on Google.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s start coding
&lt;/h3&gt;

&lt;p&gt;If I’m being honest, the first thing I did was refresh my memory about working with &lt;em&gt;environment variables&lt;/em&gt; file. Those are variables whose value is set outside of the program. This is a critical step because the customers will use their own email recipients and their own file paths, and they shouldn’t need to open the code in order to set those.&lt;/p&gt;

&lt;p&gt;ENV_VARS.env:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
shoe-maker.py:&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
So what do we see in the screenshots above? First of all, we see that I called my program shoe-maker.py, and you might wonder why. I will tell you at the end of this blog post.

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;lines 6–7 are for directing the program to the location of the &lt;em&gt;.env&lt;/em&gt; file. By the way, you can call it whatever you want, but ENV_VARS is the standard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;lines 9–12 are saving the values we extracted from the file into local variables. Grabbing the values externally every time we want to use them is expensive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, lines 1–4 are all the imports the IDE complained you are missing. ;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Wait a minute — PASSWORD in the env vars?
&lt;/h3&gt;

&lt;p&gt;Let’s reflect on that screenshot for a minute. Despite being worried about compromising the email account, the app password is out and about in the env vars file. How come?&lt;/p&gt;

&lt;p&gt;This is because I was mixing up who my audience is. I knew I was going to write this blog post, and I wanted to give you (people from the software field) the software with which you can set up your own email sender (and app password).&lt;/p&gt;

&lt;p&gt;The problem is, that this is &lt;em&gt;a different goal&lt;/em&gt; than providing a final product aimed at “real” users. Real users shouldn’t be exposed to any password, and it’s better to have as few env vars as possible so that the user will have fewer opportunities to make oopsies in the configuration.&lt;/p&gt;

&lt;p&gt;So, from this point forward, I am setting the email address and password within the code.&lt;/p&gt;

&lt;p&gt;Straightaway, after figuring out the env vars, I stopped and moved to step 3. I did it because I wanted to ensure I could successfully export the &lt;em&gt;.py&lt;/em&gt; file into a &lt;em&gt;.exe&lt;/em&gt; file before I invested any additional time writing in Python. As I mentioned before, .exe file was an essential goal of this project, and I was willing to switch to another programming language if I couldn’t achieve it there.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Extract The Code to .exe File
&lt;/h2&gt;

&lt;p&gt;Thanks to YouTube, I found &lt;a href="https://www.youtube.com/watch?v=QWqxRchawZY" rel="noopener noreferrer"&gt;this&lt;/a&gt; tutorial. Long story short, you need to install &lt;a href="https://pypi.org/project/pyinstaller/" rel="noopener noreferrer"&gt;PyInstaller&lt;/a&gt;. Pretty easy to do, I’ll just mention that for me, when I used the regular pip install pyinstaller I got an error (CommandNotFoundException) and was able to bypass it with py -m pip install pyinstaller.&lt;/p&gt;

&lt;p&gt;Could I’ve gone to figure out how to make pip the command work directly without the py -m? Yes. Was I going to do it while I was in the fun flow of my email-sending program? Nope. :D&lt;/p&gt;

&lt;p&gt;Speaking of errors, when I was trying to verify the installation went smoothly with the -v flag, I got an “I don’t know what are you talking about” error, and it took me too long to realize it was because it requires a camel case in the word PyInstaller:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Once you have it installed, you can create the .exe file by running py -m PyInstaller --onefile --icon=image.ico my_file.py. This way you create a my_file.exe file with the icon of your choice, using the code from my_file.py. Note that it has to be a .ico file. I used &lt;a href="https://convertio.co/png-ico/" rel="noopener noreferrer"&gt;this&lt;/a&gt; website to convert my image from jpg. After a few seconds, you’ll see a new dist folder in your directory with your new .exe file.

&lt;p&gt;Now that step 3 is behind us — we can continue with our code!&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%2F7xicoi03q2azf8ozy51g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xicoi03q2azf8ozy51g.png" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Back to Sending an Email Through Code
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Sending a simple email
&lt;/h3&gt;

&lt;p&gt;To focus on email sending, I temporarily set the variables in the program, instead of moving between files. You can see that in line 2 in the screenshot below. Actually, at this stage, I didn’t even include files — I just wanted to see that I could send the most simple email.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Lines 4,17 are for catching exceptions, which I will touch on later.

&lt;p&gt;I used the email-sending code from &lt;a href="https://www.youtube.com/watch?v=JRCJ6RtE3xU" rel="noopener noreferrer"&gt;this&lt;/a&gt; video. You might noticed I renamed my sender email from EMAIL to GMAIL_ADDR. This is a good reminder that the code is set up to use the Gmail server (line 5). If my program was meant for people from the software world to use, setting up their own server (that might not be Gmail), I wouldn’t have hard-coded it. But for my use case, even if I have more users within my family, Gmail is good enough.&lt;/p&gt;

&lt;p&gt;So, I started the connection (lines 6,7), logged in with the credentials (line 8), wrote a simple email (lines 10–13), and sent it (line 14). Lastly, on line 16, I notified the software user that the email was sent successfully (if we were on the &lt;a href="https://dictionary.cambridge.org/dictionary/english/happy-path" rel="noopener noreferrer"&gt;&lt;em&gt;happy path&lt;/em&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is what it looks like on the recipient’s side:&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%2Fvb2nv302iizzthoqgcps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvb2nv302iizzthoqgcps.png" alt="It worked!" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Sending email to multiple recipients
&lt;/h3&gt;

&lt;p&gt;This is nothing complex, I am just showing the order in which I progressed. Sending the email to multiple recipients is easily done by a simple for loop, over the ‘recipients’ variable, which is an array of email addresses (strings).&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing usage of env vars file
&lt;/h3&gt;

&lt;p&gt;I know the pieces work well separately, but it doesn’t guarantee they will work well together too. So, at this stage, the program runs smoothly when I set the recipients in the env vars file, but later on, the env vars are going to cause one of my biggest bugs. Any guess at which point?&lt;/p&gt;
&lt;h3&gt;
  
  
  Error handling
&lt;/h3&gt;

&lt;p&gt;I know that adding error handling is a bit excessive, considering I only plan to have between 1–2 customers (who are obligated to love me unconditionally). What can I say? I have a standard for coding, and I can’t half-bake it. Now that I think about it, &lt;a href="https://www.techtarget.com/searchnetworking/definition/graceful-degradation#:~:text=Graceful%20degradation%20is%20the%20ability,is%20to%20prevent%20catastrophic%20failure." rel="noopener noreferrer"&gt;graceful failure&lt;/a&gt; is important in this case because the user might update the env vars file incorrectly and will need some guidance in order to fix it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
So, what do we have here?

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In lines 3,5 I added colors because I wanted to differentiate between the error message and the instructions for the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the instructions (lines 6–8), I informed the user that this process failed and that they would need to send an email themselves. Also, I invited them to contact support, and lastly — to close the program.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I split the instructions into 3 lines because it’s easier to read for the developer (rather than to scroll right).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason I added the input() in the end is so that the user will have as much time as they need to read the messages.&lt;/p&gt;

&lt;p&gt;I’ve added more error handling later on, but let’s continue the story in chronological order.&lt;/p&gt;
&lt;h3&gt;
  
  
  Refactoring
&lt;/h3&gt;

&lt;p&gt;This refactoring is coming from the tutorial I linked earlier. We have two changes to make:&lt;/p&gt;

&lt;p&gt;First, If you swap line 10: &lt;br&gt;
with smtplib.&lt;strong&gt;SMTP&lt;/strong&gt;(‘smtp.gmail.com’, &lt;strong&gt;587&lt;/strong&gt;) as smtp: with &lt;br&gt;
with smtplib.&lt;strong&gt;SMTP_SSL&lt;/strong&gt;(‘smtp.gmail.com’, &lt;strong&gt;465&lt;/strong&gt;) as smtp: &lt;br&gt;
then you can remove lines 11,12 because they now happen behind the scenes.&lt;/p&gt;

&lt;p&gt;What &lt;em&gt;exactly&lt;/em&gt; is happening behind the scenes? &lt;strong&gt;SMTP_SSL&lt;/strong&gt; connects to the server using a secure encrypted SSL protocol (SSL stands for &lt;em&gt;Secure Sockets Layer&lt;/em&gt;,), while &lt;strong&gt;SMTP&lt;/strong&gt; doesn’t, which is why we need to add the starttls() function right after connecting. ehlo() function is for the server to say hello and identify :)&lt;/p&gt;

&lt;p&gt;Note that the different connections use different ports (&lt;strong&gt;587&lt;/strong&gt; vs. &lt;strong&gt;465&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;The second refactoring is happening in the message content: Swapping &lt;br&gt;
msg=f’Subject: {subject}\n\n{body}’ &lt;br&gt;
in line 18 with a more comfortable structure. This is done by using EmailMessage import.&lt;/p&gt;

&lt;p&gt;Using the new import, we set the message object like this:&lt;br&gt;
msg = EmailMessage()&lt;br&gt;
msg[‘Subject’]=subject&lt;br&gt;
msg.set_content(body)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most important thing in refactoring is checking we didn’t botch anything that used to work fine :D&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Still working! Now we can progress to step 2.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Attaching Files to The Email
&lt;/h2&gt;

&lt;p&gt;Let me remind you, that the whole purpose of this project was to send Excel files. Adding an attachment to the email is pretty straightforward: Given the file path, you gather the variables needed for the msg.add_attachment(…) function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
And for multiple files — just wrap this code in a loop.

&lt;p&gt;That’s cool if we have the paths in the correct form. Unfortunately, it turns out that gathering the paths from the env vars file wasn’t as easy as I thought it would be:&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%2Fg8oq2asl5ru6hp6gr547.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8oq2asl5ru6hp6gr547.png" alt="File paths error" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Encountering My First Major Bug
&lt;/h3&gt;

&lt;p&gt;What is this error saying? The error is rising from the beautiful ast.literal_eval(..) function. This function parses strings representing types, into the actual type (here we use it for arrays). In this case, the function can’t parse my file paths string from the env vars file because of &lt;strong&gt;Unicode escape errors&lt;/strong&gt;: My file path location (C:&lt;strong&gt;\U&lt;/strong&gt;sers\…) triggers the function to convert the string to the format of a 4-digit hexadecimal code point (for example, “ABC” becomes “\u0041\u0042\u0043”), which makes the path absurd.&lt;/p&gt;

&lt;p&gt;One solution for this problem is adding the r prefix to all the paths (r"&lt;a href="mailto:myfriend@gmail.com"&gt;myfriend@gmail.com&lt;/a&gt;"). This r stands for “raw”, and with it, the string will be read exactly as it is written. I didn’t want to go that route because I don’t think it’s a good practice to “force” the user to adjust to my program's limitations. The users should add file paths as they see it in the file explorer and the conversion to the right format should be done inside the code.&lt;/p&gt;

&lt;p&gt;With that in mind, this is the solution I wrote:&lt;/p&gt;

&lt;p&gt;First, I save the env var as a string (including brackets, commas, ditto marks (“), and white spaces). Then, I removed the brackets and split the string by the commas. Now I have an array of strings, but they still begin and end with ditto marks. This brings me to my final line of code: Strip each of the strings of any ditto marks and white spaces. In case you wondered, I’m handling white spaces because some people like to write lists with white spaces after the commas.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that the paths’ backslashes have converted from \ to \ automatically, in order to avoid those escape characters I mentioned before.

&lt;p&gt;So far we have sent an email, added attachments, and exported the program to .exe… At this point, we can put the .exe file on the desktop and send emails at the click of a button! All we have left to do is to automate that task.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Automate a Daily Send
&lt;/h2&gt;

&lt;p&gt;Luckily for us, Windows has a program called &lt;strong&gt;&lt;em&gt;Task Scheduler&lt;/em&gt;&lt;/strong&gt;. It’s already on your laptop! It took me a bit of learning to understand how to get it to do what I wanted it to do, but I made it :D&lt;/p&gt;

&lt;p&gt;This is what you need to know: First, it is good practice to create a directory for your tasks (“My Tasks”) because other scheduled tasks are running on your computer and you don’t want to mess with them!&lt;/p&gt;

&lt;p&gt;Once you are in your new directory -&amp;gt; click the Create Task button.&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%2Fhh961gh5p5tif7fywu4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhh961gh5p5tif7fywu4o.png" alt="Task scheduler" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a new task
&lt;/h3&gt;

&lt;p&gt;Now you will see a window with 5 tabs, we will go over them one by one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;General:&lt;/strong&gt; Give the task a good name, and tick off the &lt;em&gt;“Run whether the user is logged on or not”&lt;/em&gt;. This is because we want the task to run daily, regardless of whether you are on your laptop or not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Triggers:&lt;/strong&gt; What will initiate our .exe file? The time of the day. Choose your most comfortable hour, set it to “daily”, and don’t forget to tick off the &lt;em&gt;“Stop task if it runs longer than”&lt;/em&gt; option. Our program only takes a few seconds and if it’s running longer it means it’s stuck — and we should kill it.&lt;/p&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%2F14ev816wdxs99cifpcut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14ev816wdxs99cifpcut.png" alt="Adding a trigger in the task scheduler" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actions:&lt;/strong&gt; This is the juicy part! Here you put the path to your .exe file. Let me save you some precious time, and tell you this is where the program, as it is now — will fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue is that the task scheduler doesn’t run the program from the same directory of the file. This is what I mean: Instead of running the command like this&lt;br&gt;
C:\Users\ &lt;strong&gt;Desktop\shoe-maker&lt;/strong&gt; &amp;gt; py .\shoe-maker.py&lt;/p&gt;

&lt;p&gt;Imagine running it like this: &lt;br&gt;
C:\Users&amp;gt; py .\ &lt;strong&gt;Desktop\shoe-maker&lt;/strong&gt; \shoe-maker.py&lt;/p&gt;

&lt;p&gt;We coded the env vars file location &lt;strong&gt;relatively&lt;/strong&gt;, and we don’t know where the task scheduler is executing the program from, therefore — we need to add the full path as an &lt;strong&gt;argument&lt;/strong&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conditions:&lt;/strong&gt; It’s important to tick off the &lt;em&gt;“Wake the computer to run this task”&lt;/em&gt; option so that the automation doesn’t depend on the user’s laptop usage.&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%2Flzrr8ylcu8d07v95xlt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzrr8ylcu8d07v95xlt7.png" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it! I recommend setting it first to 1 minute instead of 1 day just to test that it works :)&lt;/p&gt;

&lt;p&gt;Are we done now? Not exactly.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Privacy Gap Discovered
&lt;/h2&gt;

&lt;p&gt;Remember earlier, when I easily made the decision to open a dedicated Gmail account, in order not to compromise any existing Gmail account’s security (due to usage of app passwords)?&lt;/p&gt;

&lt;p&gt;It looked like a no-brainer solution, and I didn’t realize that using an external Gmail account meant exposing the data (the precious Excel files) to anyone with access to this account (like me!)&lt;/p&gt;

&lt;p&gt;Did you notice how WhatsApp keeps reminding you that your messages are end-to-end encrypted, and your message content stays between you and your conversation partner? I aim to provide the same.&lt;/p&gt;

&lt;p&gt;Unfortunately, I couldn’t find a way to do so. This is a compromise I’m not happy about, but I still prefer that over risking an actual active email address that has gathered years of data.&lt;/p&gt;

&lt;p&gt;A nice settlement had been setting on Gmail a filter to send every email that is not part of the development (aka not “from me to me”) directly to “Trash”. Messages will sit there for 30 days and then will be automatically deleted. In the real world, this is not a good enough solution, but my 2 customers were okay with the terms and conditions :)&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Final Touches
&lt;/h2&gt;

&lt;p&gt;Now that we have the functionality, we just need to add some final touches, to make it cool:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Icon:&lt;/strong&gt; That is the first thing I added, as soon as I set up the .exe export, but maybe for you, this step is coming only now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meaningful email title and body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I started by creating a new env var, to let the user title the email with something meaningful. In addition to that, I created a default title, in case they don’t want to set it up (lines 2,4).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On line 6 I set a fun email body.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On lines 8–16, I used the option to add alternative text, in HTML format.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Additional error handling:&lt;/strong&gt; First, I considered what errors the user might be creating. Then I caught them, and added instructions:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Once you’ve added the code to print the error, it’s pretty easy to start covering specific cases: Make a mess and copy the error that comes out. Then, add it as a case in the catching :D

&lt;p&gt;&lt;strong&gt;Adding a progress bar:&lt;/strong&gt; It’s nice to keep the user in the loop and show them the progress. Why should we message the user only when things are bad? ;)&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  7. Developer Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Here is a short list of errors you might encounter that didn’t come up in the blog post flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;(334, b’UGFzc3dvcmQ6'): This is an SMTP authentication error, and it comes up when the email or password you entered is incorrect. The reason behind it might be a typo in the actual email and password, but it can also be a typo in the env vars &lt;em&gt;variable names&lt;/em&gt; for those fields! So watch out for that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PermissionError: [WinError 5] Access is denied: exe window was open: This one happens when you try to create a new .exe version (which overrides the previous one), but you don’t exit the program. Is this the reason I added the You may exit this program. instruction to my error handling? Maybe :)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  8. Feature request: Add directories (as well as direct file paths) to be sent
&lt;/h2&gt;

&lt;p&gt;Our program is already able to send files given their path on the computer. &lt;br&gt;
The plan is to loop over the given directory paths and create file paths from those. Then those file paths can work with the rest of the program.&lt;br&gt;
We'll start by creating a temporary file, which will be our playground to get from a directory path to a list of file paths.&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%2Fb9262880etb8os56ukz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9262880etb8os56ukz5.png" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we'll go to the section in the code where we attach the files and check if the path is a directory (there is a function for that!), and if it is, get the file names, and for each: create a full path and attach the file.&lt;br&gt;
This will require exporting the code for attaching the file to a separate function, and calling it in each of the scenarios: if the path is a file or a directory.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why Shoemaker?
&lt;/h2&gt;

&lt;p&gt;First of all, I’m glad that you made it this far in the blog post! Hopefully, this inspired you to start your own little project. It surely made me :D&lt;/p&gt;

&lt;p&gt;I named this project &lt;strong&gt;The Shoemaker&lt;/strong&gt; because of the saying &lt;em&gt;“The shoemaker’s children go barefoot”&lt;/em&gt;. I found it fitting to the scenario of the software engineer's parents doing daily tasks that can be automated via code. ;)&lt;/p&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=1YXVdyVuFGA" rel="noopener noreferrer"&gt;How to Setup Gmail SMTP Server&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://youtu.be/yuOK6D7deTo" rel="noopener noreferrer"&gt;How To Set Up SMTP Server In Gmail&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=JRCJ6RtE3xU" rel="noopener noreferrer"&gt;How to Send Emails Using Python — Plain Text, Adding Attachments, HTML Emails, and More&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/jakewitcher/using-env-files-for-environment-variables-in-python-applications-55a1"&gt;Using .env Files for Environment Variables in Python Applications&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.freecodecamp.org/news/command-line-for-beginners/#differencebetweenconsolecommandlinecliterminalandshell" rel="noopener noreferrer"&gt;Difference between console, terminal, command line (CLI)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=QWqxRchawZY" rel="noopener noreferrer"&gt;Standalone Python EXE Executable — Python Tkinter GUI Tutorial&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you read this, you’ve reached the end of the blog post, and I would love to hear your thoughts! Here are the ways to contact me:&lt;br&gt;
Facebook: &lt;a href="https://www.facebook.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.facebook.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Instagram: &lt;a href="https://www.instagram.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.instagram.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Email: &lt;a href="mailto:cupofcode.blog@gmail.com"&gt;cupofcode.blog@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Create Your Own Website — Is It Worth It?</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Sat, 26 Aug 2023 20:31:57 +0000</pubDate>
      <link>https://forem.com/cupofcode/create-your-own-website-is-it-worth-it-36bm</link>
      <guid>https://forem.com/cupofcode/create-your-own-website-is-it-worth-it-36bm</guid>
      <description>&lt;h3&gt;
  
  
  All the pros and cons, all the whys and the hows — in one place :D
&lt;/h3&gt;

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

&lt;p&gt;Before the &lt;em&gt;How —&lt;/em&gt; let’s start with the &lt;em&gt;Why:&lt;/em&gt; Why should you create a website? Because it doesn’t matter what you do for a living, even if you don’t need to attract customers in your profession, it’s pretty cool to be able to send people to a space that represents you. So, whether it’s a one-pager resume with contact information, or a collection of blog posts, recipes, or any hobby that you want to share with the world — a website of your own is a nice way of doing that.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Bit of Background
&lt;/h3&gt;

&lt;p&gt;I started &lt;em&gt;Cup of Code blog&lt;/em&gt; on Aug 2019 (yes, it just turned 4 years old :D). I wrote blog posts on &lt;a href="https://cupofcode.medium.com/" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;, and a year later I saw I loved it, and was ready to take it to the next level — Create my own &lt;a href="https://cupofcode.blog/" rel="noopener noreferrer"&gt;external website&lt;/a&gt;! So, without any previous experience but with lots of motivation, I maneuvered my way to a functioning website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2APr0diVVf1o9tLcEmp-8Mcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2APr0diVVf1o9tLcEmp-8Mcg.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One day, while I was dealing with moving apartments — it turned out my website needed to move too! I suddenly got a surprising &lt;strong&gt;$400+&lt;/strong&gt; charge from my hosting service! This made not just me worried, but my bank as well — which resulted in them blocking my credit card! (You know how it goes, trouble never comes alone)&lt;/p&gt;

&lt;p&gt;Why did I not expect this $400 charge? This is a wonderful segue for diving into my &lt;strong&gt;website hosting tips and tricks&lt;/strong&gt;!!&lt;/p&gt;

&lt;p&gt;Important note: While doing the research for my own case, I found it hard to reach &lt;em&gt;unsponsored&lt;/em&gt; content — which made me doubt the integrity of the reviews. Let me assure you — this blog post (and my blog in general) is NOT sponsored. I blog as a hobby, and I appreciate the autonomy of writing whatever I want, whenever I want.&lt;/p&gt;

&lt;p&gt;Now we can start :D&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%2F14vvu4aveuhi48rdomtq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14vvu4aveuhi48rdomtq.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #1: Know All The Costs!
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Owning a website is not just pure hosting.&lt;/strong&gt; It is composed of several parts that I will go over here in the blog post. &lt;em&gt;Learn all the moving parts, and the total cost of your website before deciding if you are willing to go that route!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I will divide it into 3 parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Hosting: HTML pages, Pictures… Where do your files sit? There are &lt;strong&gt;many&lt;/strong&gt; hosting services out there, and it can be overwhelming. I’ll talk more about it in tip #2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain: What do people type in the URL to reach your website? Are you willing to spend money on a “clean” domain, or are you okay with a domain like &lt;a href="https://cupofcode.medium.com/" rel="noopener noreferrer"&gt;https://cupofcode.medium.com/&lt;/a&gt; (under Medium) or &lt;a href="https://graphcsv.netlify.app/" rel="noopener noreferrer"&gt;https://graphcsv.netlify.app/&lt;/a&gt;* (hosted with Netlify)? More about it in tip #3.&lt;br&gt;
*I created this website for my &lt;a href="https://cupofcode.blog/alicecode/" rel="noopener noreferrer"&gt;Alicecode&lt;/a&gt; class to demo a project with CSV files ;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Website features: If you decide to use WordPress, you will see there are a bunch of plugins you can purchase. It is tempting, and you should count it in when calculating the total costs of your website. I’ll explain it all in tip #4.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is important to mention that all those parts are &lt;strong&gt;subscriptions&lt;/strong&gt;— you’ll be charged &lt;em&gt;periodically&lt;/em&gt; &lt;strong&gt;forever!&lt;/strong&gt; Well, not really forever, but as long as you want to keep your website up. In the end, I’ll share my yearly costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #2: How To Choose The Right Hosting
&lt;/h3&gt;

&lt;p&gt;This is where you can really get decision fatigue: Not only there are so many hosting services, but each of them has multiple plans! TOO MANY OPTIONS!&lt;/p&gt;

&lt;p&gt;All plans have an introductory price and then a full price after the first period. My most important tip here is to not be fooled by the introduction price - Pick a plan that you are willing to pay the &lt;em&gt;full price&lt;/em&gt; for. Yes — This is where the surprise $400 bill came from!&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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2Avoqodn7criqH6n3oxIwsIQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2Avoqodn7criqH6n3oxIwsIQ.png" alt="[Bluehost plans](https://www.bluehost.com/wordpress/wordpress-hosting)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I first started with &lt;a href="https://www.bluehost.com/wordpress/wordpress-hosting" rel="noopener noreferrer"&gt;Bluehost&lt;/a&gt;, because they are big, reliable, and beginner-friendly. I chose the &lt;em&gt;Basic Web Hosting *and paid ~$100 for the 3-year plan. That’s a nice price. The problem comes with *“Auto renews at the regular rate”&lt;/em&gt;, and $11/mo was too much (for me).&lt;/p&gt;

&lt;p&gt;So, despite the inconvenience, and after some research, I migrated my website to the &lt;em&gt;Single hosting plan&lt;/em&gt; in &lt;a href="https://www.hostinger.com/web-hosting" rel="noopener noreferrer"&gt;Hostinger&lt;/a&gt;, because they are also big and reliable, and $4/mo is the cheapest I could find.&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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2Atvl05a80Rm_wEoraBgLM_A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2Atvl05a80Rm_wEoraBgLM_A.png" alt="[Hostinger plans](https://www.hostinger.com/web-hosting)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I attached here only 2 hosting services with a total of 8 plans, and it is already looking very confusing. You may be wondering why don’t I pay an extra tiny $1/mo and get &lt;strong&gt;100 websites&lt;/strong&gt; — Well, because I don’t need 100 websites! Also, don’t forget how quickly this $3/mo turns into $7/mo &lt;strong&gt;forever&lt;/strong&gt;. Lastly, even if you do eventually need additional websites, you can always upgrade.&lt;/p&gt;

&lt;p&gt;Whatever hosting service you choose, you’ll be able to save some more coins with promo codes. You can find plenty on YouTube: Just search for a review of the hosting service of your choice and most chances you’ll land on a sponsored video ;) In Bluehost, I got free Domain name registration + domain privacy&amp;amp;protection for 1 year (which saved me ~$50), and in Hostinger I got 10% off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Besides The Plans&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I talked about costs, but do you know what is even more important than saving some money? &lt;strong&gt;Good customer service!&lt;/strong&gt; No doubt you are all very smart, but it doesn’t mean you need to figure out things by yourselves: When you are tackled with a surprise charge, or when you have problems migrating your website to another hosting service… The smartest thing you can do is save time.&lt;/p&gt;

&lt;p&gt;I don’t know much about others, but I had a wonderful experience both with Bluehost and Hostinger. I got a human being in the chat within 3 minutes every time!&lt;/p&gt;

&lt;p&gt;The last thing to mention when it comes to hosting is choosing the right &lt;strong&gt;server location&lt;/strong&gt;. It is hard to predict where will your traffic come from, but a good start will be your own location (because your audience will probably begin with your circle of acquaintances, right?). I don’t remember an option to choose when I set up my hosting with Bluehost, but with Hostinger I chose Europe and it’s loading faster :D&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #3: Don’t cheap out on domain extras!
&lt;/h3&gt;

&lt;p&gt;Extras? Wait, what are the domain &lt;em&gt;basics&lt;/em&gt;? Don’t worry, I’ll cover everything you need to know about the domain here.&lt;/p&gt;

&lt;p&gt;When you choose your domain provider, you’ll see a text box where you fill in your future website’s name, and next to it you’ll have a drop-down with possible suffixes: .com, .blog, .org, etc.&lt;/p&gt;

&lt;p&gt;I’m no expert, but my experience shows that the more popular suffixes (.com for example) will be more expensive. I liked the idea that you can tell my URL is a blog before you even click (&lt;a href="https://www.cupofcode.blog" rel="noopener noreferrer"&gt;https://www.cupofcode.blog&lt;/a&gt;) but be aware that people trust “.com” more. Invest efforts in choosing the right URL — because this is where people will go when they want to reach your website, and it’s not something you should plan on changing.&lt;/p&gt;

&lt;p&gt;Lastly, as I mentioned before — some hosting services will give you a free domain for a year, or something similar — so keep an eye out for that!&lt;/p&gt;

&lt;p&gt;Now that we finished with the basics — let’s talk about the extras: You choose your perfect URL, you accept the price it costs, click next — and discover there is an optional extra: Domain privacy+protection.&lt;/p&gt;

&lt;p&gt;All the domains (no matter the provider you choose) are managed by an organization called ICANN. They are required by law to &lt;strong&gt;publicly&lt;/strong&gt; list the contact information of every domain owner in a big database called &lt;a href="https://who.is/" rel="noopener noreferrer"&gt;WHOIS&lt;/a&gt;. Your domain provider can mask this information and keep your privacy. I think this is important, but you do you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transferring Domains&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(Feel free to skip this part if you are a first-time user)&lt;/p&gt;

&lt;p&gt;When you move from one hosting service to another, you can (but don’t have to) move your domain registerer as well. I decided to do so because I find it convenient to have it all under one roof. Remember I told you I moved apartments? Thanks to &lt;a href="https://www.youtube.com/watch?v=W9Fx5EMAn38" rel="noopener noreferrer"&gt;this video&lt;/a&gt; I learned you &lt;strong&gt;can not&lt;/strong&gt; migrate your domain if you changed your contact info (that includes your address) in the last 60 days! So I changed the address after… Phew! Another piece of information he noted in that video was that migrating automatically adds a year to your expiration date (which means you pay money) — I don’t find this critical, but good to know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #4: You can write code, but should you?
&lt;/h3&gt;

&lt;p&gt;Now that you have hosting and a domain name, you need to take care of the content! When I started — everywhere I looked, people used &lt;a href="https://en-gb.wordpress.org/" rel="noopener noreferrer"&gt;WordPress&lt;/a&gt;. WordPress is a content management system (CMS), which means it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2AwivtkciZp2zwMHA_5hiMUw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2AwivtkciZp2zwMHA_5hiMUw.png" alt="[https://en-gb.wordpress.org/](https://en-gb.wordpress.org/)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you log into your WordPress account, you’ll see this dashboard. The magic happens when you add good plug-ins, some are free and some are worth the money.&lt;/p&gt;

&lt;p&gt;There are many WordPress tutorials out there, you don’t need me to add another one. I’ll just tell you that for some annoying reason, there are 2 WordPress-es out there: &lt;strong&gt;WordPress.org&lt;/strong&gt; and &lt;strong&gt;WordPress.com&lt;/strong&gt;. &lt;em&gt;.org&lt;/em&gt; is the open-source, free-to-download management platform I was talking about, and &lt;em&gt;.com&lt;/em&gt; is a fully hosted platform, meaning you get everything in one package. If you are getting hosting separately — WordPress.org is your answer.&lt;/p&gt;

&lt;p&gt;Now, I know how to build web pages but chose not to. I wanted to invest my time in the content. Therefore, I’ve decided to use &lt;a href="https://elementor.com/products/hosting-website-builder/" rel="noopener noreferrer"&gt;Elementor&lt;/a&gt;, which is nice because you can easily set it up with drag&amp;amp;drop elements. They offer a free tier, but I got the Pro Essential plan for ~$50/yr because of widgets such as forms and posts. The comparison between Elementor plans can be found &lt;a href="https://elementor.com/help/elementor-pro-vs-free/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. With Elementor Pro, after I followed &lt;a href="https://www.youtube.com/watch?v=F9UBPbsZ2Rs" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;, my website turned out beautiful and I no longer need to make adjustments every time I publish a new blog post.&lt;/p&gt;

&lt;p&gt;Now, I know how to build web pages — but I chose not to. I wanted to invest my time only in the content. I’ve decided to use &lt;a href="https://elementor.com/products/hosting-website-builder/" rel="noopener noreferrer"&gt;Elementor&lt;/a&gt; because I got a recommendation. Maybe they have a free tier, I use the pro version for ~$50/yr. The fun part here is that you can easily set it up with* drag&amp;amp;drop *elements, and after I followed &lt;a href="https://www.youtube.com/watch?v=F9UBPbsZ2Rs" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;, My website turned out beautiful, and ever since doing that, I don’t need to make any adjustments every time I publish a new blog 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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2ADNQXC1FJLHHbB8vf9yFg-A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2ADNQXC1FJLHHbB8vf9yFg-A.png" alt="[Elementor plugin plans](https://elementor.com/pricing-plugin/), priced appear in ILS for some reason, but the Essential plan on the left is $49, which is what I have" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;With that said, I have a friend who now asked for my help with &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt;, which is a free platform to build clean static pages. Who knows, maybe I’ll discover this is a better option for a blog (Stay tuned!). Note that this requires a learning curve and time investment compared to Elementor.&lt;/p&gt;

&lt;p&gt;Yup, it always boils down to time vs. money ;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #5: Don't fall into “first-year free” traps!
&lt;/h3&gt;

&lt;p&gt;Soon enough after the migration from Bluehost to Hostinger was done, I received an email encouraging me to use my “free email address”.&lt;/p&gt;

&lt;p&gt;Their email was written in a pretty convincing way: &lt;em&gt;“Hi there, It’s tough to be taken seriously with a personal email address such as &lt;a href="//mailto:proconsulting1@gmail.com"&gt;proconsulting1@gmail.com&lt;/a&gt;. … The good news is that your hosting order comes with a free business email! Here are a few reasons why it pays to use a business email at your domain…”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So you probably think “Hey, why not try? It’s free”… Because this is a trap!! Think about it, an email address is an actual address — it is where people go when they want to reach you. It is hard, maybe even impossible, to redirect all the emails to a free alternative once the free year is over. I wouldn’t want to be trapped with renewing this email address &lt;strong&gt;forever&lt;/strong&gt;, even for a small price.&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%2Fl8dlyxzxlfjyesq0r1am.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8dlyxzxlfjyesq0r1am.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #6: You will spend a “learning fee” — and it is ok!
&lt;/h3&gt;

&lt;p&gt;This is wonderful advice from my Mom. She wasn’t in on my Website endeavors, she was saying it about &lt;a href="https://blog.devgenius.io/my-6-months-mark-celebration-speech-c50e9b754832" rel="noopener noreferrer"&gt;my relocation&lt;/a&gt;— but it fits here as well: You are doing something new, and you don’t know what is the correct/fastest/cheapest way &lt;strong&gt;yet&lt;/strong&gt; — and it is ok! This is your learning fee, embrace it and have compassion for yourself. :)&lt;/p&gt;

&lt;h3&gt;
  
  
  To conclude... Is it worth it?
&lt;/h3&gt;

&lt;p&gt;Unfortunately, you are a grown-up and therefore this is a decision you will need to make for yourself. Many hobbies will require costs if you want to step it up, and blogging isn’t different. My current yearly cost is ~$120, broken down as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hosting: ~$24/yr for the next 4 years, and then ~$48/yr&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain+privacy+protection: ~$45/yr (the domain alone is ~$23)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Elementor: ~$50/yr&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also pick and choose where you spend your money — I gave free alternatives throughout the blog post.&lt;/p&gt;

&lt;p&gt;My guideline is — if you are curious about it, and you can afford it — SURE! Give it a go. If you don’t like it, it isn’t really forever, despite my using this word a lot today. You can always cancel the &lt;strong&gt;automatic&lt;/strong&gt; renewal -&amp;gt; Here is another tip for you, so you won’t receive an unexpected $400 charge!&lt;/p&gt;




&lt;p&gt;If you read this, you’ve reached the end of the blog post, and I would love to hear your thoughts! Here are the ways to contact me:&lt;br&gt;
Facebook: &lt;a href="https://www.facebook.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.facebook.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Instagram: &lt;a href="https://www.instagram.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.instagram.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Email: &lt;a href="mailto:cupofcode.blog@gmail.com"&gt;cupofcode.blog@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AtJ34Advmqvd2F7Jq.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="700" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>career</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Introduction to AWS — Cloud Best Practices</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Mon, 08 Aug 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/cupofcode/introduction-to-aws-cloud-best-practices-23ea</link>
      <guid>https://forem.com/cupofcode/introduction-to-aws-cloud-best-practices-23ea</guid>
      <description>&lt;h3&gt;
  
  
  The cloud is useful for flexibility, efficiency, scalability, and security — especially when you use it correctly ;)
&lt;/h3&gt;

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

&lt;p&gt;From the person who gave you varieties of only part-ones (&lt;a href="https://cupofcode.medium.com/the-main-building-blocks-of-an-angular-application-explained-cup-of-angular-part-1-dce71c88d449" rel="noopener noreferrer"&gt;Angular&lt;/a&gt;, &lt;a href="https://cupofcode.medium.com/a-non-scary-introduction-to-computer-networking-cup-of-networks-part-1-9f76583dc8ca" rel="noopener noreferrer"&gt;Networking&lt;/a&gt;), I’m very excited to present the &lt;strong&gt;seventh&lt;/strong&gt; and last blog post of the AWS introduction series!!&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%2F51q5o8enuuj0q51sy0k0.jpeg" 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%2F51q5o8enuuj0q51sy0k0.jpeg" alt="meme" width="450" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today we will talk about a variety of techniques to use AWS services to meet your needs in the best way, including scalability, automation, and more! Here is a table of content for easy access:&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%2F46u9gqeqsmjjmpd06hwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46u9gqeqsmjjmpd06hwj.png" alt="Table of content" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The content of this blog post is based on the course &lt;a href="https://learn.acloud.guru/course/aws-certified-cloud-practitioner/dashboard" rel="noopener noreferrer"&gt;AWS Certified Cloud Practitioner 2020 by a cloud guru&lt;/a&gt; and on the whitepapers &lt;a href="https://d1.awsstatic.com/whitepapers/architecture/AWS_Well-Architected_Framework.pdf" rel="noopener noreferrer"&gt;Well architected framework&lt;/a&gt; and &lt;a href="https://d1.awsstatic.com/whitepapers/aws-overview.pdf" rel="noopener noreferrer"&gt;AWS overview&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  High Availability and Scalability
&lt;/h2&gt;

&lt;p&gt;Let’s start with scalability and see how it connects to high availability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt; means that a system can handle greater loads by adapting. There are two types of scalability: vertical and horizontal (=elasticity).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Vertical Scalability&lt;/em&gt;&lt;/strong&gt; is scaling up/down: Upgrading/downgrading the instance, like getting a new fancy phone.&lt;br&gt;
It is common for &lt;strong&gt;non-distributed systems&lt;/strong&gt;, which makes sense because this is when you &lt;strong&gt;can not add&lt;/strong&gt; a machine and split the data.&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;A side note about distributed systems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A distributed system&lt;/em&gt;&lt;/strong&gt; is a computing environment in which various components are spread across &lt;strong&gt;multiple&lt;/strong&gt; computers on a network. In a non-distributed system, all the parts of the system are in the same physical location.&lt;/p&gt;

&lt;p&gt;As I see it, if you need to use the internet to communicate between your components — it’s distributed. That means, that &lt;strong&gt;almost all&lt;/strong&gt; of the systems we know today are distributed, maybe besides some projects you did as a freshman in College.&lt;/p&gt;

&lt;p&gt;If you wanna learn more about distributed systems, I stumbled upon the lecture &lt;a href="https://www.youtube.com/watch?v=uTJvMRR40Ag" rel="noopener noreferrer"&gt;&lt;em&gt;Why are Distributed Systems so hard? A network partition survival guide — Denise Yu&lt;/em&gt;&lt;/a&gt; and it’s fun and full of cats!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Horizontal Scalability&lt;/em&gt;&lt;/strong&gt; is scaling out/in: Adding/ removing instances, like getting extra 3 phones! (identical to your original phone) &lt;br&gt;
This one is common for &lt;strong&gt;distributed systems&lt;/strong&gt;, and easier than vertical scaling mainly because there is no hardware limit to reach (you can always add another instance).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;High Availability&lt;/em&gt;&lt;/strong&gt; is using different locations and that’s why it goes with horizontal scaling. It means running your system in at least 2 distinct data centers (=availability zones), so you could survive a data center loss.&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%2Fxuw5v59a5thk1g08j6yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxuw5v59a5thk1g08j6yg.png" alt="meme" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Automation
&lt;/h2&gt;

&lt;p&gt;There are 3 ways to automate in AWS: Serverless management and deployment, infrastructure management and deployment, and lastly — alarms and events.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Serverless Management and Deployment&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ideally, you would like to get everything &lt;strong&gt;serverless&lt;/strong&gt; because then you don’t need to worry about infrastructure. I have to be a geek for a second and mention that “Serverless” is a misnomer (inaccurate name) in the sense that servers are still used by cloud service providers to execute code for developers, but if a server goes down it’s not on you to replace it — all you worry about is deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Infrastructure management and deployment&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AWS offers services that will scale &lt;strong&gt;automatically&lt;/strong&gt;, and all you have to worry about is your code. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Elastic Beanstalk&lt;/em&gt;&lt;/strong&gt; is a service for deploying and scaling web applications and services. The deployment, from capacity provisioning, load balancing, and auto-scaling is done automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;EC2 auto-recovery:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Automatic recovery&lt;/em&gt; migrates the instance to another hardware during an instance reboot while retaining its instance ID, private IP addresses, Elastic IP addresses, and all instance metadata.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS systems manager&lt;/em&gt;&lt;/strong&gt;, which allows you to safely automate common and repetitive IT operations and management tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;WAF (Web App Firewall) security automation&lt;/em&gt;&lt;/strong&gt;: Automatically deploys a set of AWS WAF rules that filter common web-based attacks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Alarms and events&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the first thing that pops into my head when I think about automation. A few AWS examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;CloudWatch Metric Alarms&lt;/em&gt;&lt;/strong&gt;: Classic automation, get notified when what you’re waiting for/ afraid of happens, instead of constantly checking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Cloudwatch Events&lt;/em&gt;&lt;/strong&gt;: Those are system events that describe changes in Amazon Web Services (AWS) resources. For example, get a lambda function to do something when someone uploaded a picture to your S3 bucket. It’s a way of having your environment &lt;strong&gt;proactively&lt;/strong&gt; respond to events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Lambda scheduled events&lt;/em&gt;&lt;/strong&gt;: Schedule events for every defined period of time. For example, run the lambda function at midnight, or every Monday.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Loose Coupling
&lt;/h2&gt;

&lt;p&gt;It’s easier to talk about loose coupling after explaining tight coupling, so let’s start there. &lt;strong&gt;&lt;em&gt;Tight coupling&lt;/em&gt;&lt;/strong&gt; means classes and objects are dependent on one another. So, when you change one component it could break your system quickly. With &lt;strong&gt;&lt;em&gt;loose coupling&lt;/em&gt;&lt;/strong&gt;, you design independent components, and you increase the flexibility and the resilience of the code.&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%2Frjlxz1h9yk0xslfau7ek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjlxz1h9yk0xslfau7ek.png" alt="**Tight Coupling** vs **Loose Coupling**" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are three ways to achieve loose coupling:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Well-defined interfaces&lt;/strong&gt;: For example, the &lt;strong&gt;&lt;em&gt;AWS API gateway&lt;/em&gt;&lt;/strong&gt; allows you to create your own APIs and expose them to the public internet. Interfaces are loosely coupled because the only knowledge that class A has about class B, is what class B has exposed through its interface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service discovery&lt;/strong&gt;: &lt;strong&gt;&lt;em&gt;Amazon ECS&lt;/em&gt;&lt;/strong&gt; services can be configured to use Service Discovery. Service discovery uses &lt;strong&gt;&lt;em&gt;AWS Cloud Map&lt;/em&gt;&lt;/strong&gt; API actions to manage HTTP and DNS namespaces for your Amazon ECS services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asynchronous integration&lt;/strong&gt;: When your components are loosely coupled, you can work with queues, and when one instance fails, for example, another one can come up and pull requests.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Anti-Patterns in DBs
&lt;/h2&gt;

&lt;p&gt;I have &lt;a href="https://cupofcode.blog/introduction-to-aws-databases-and-more/" rel="noopener noreferrer"&gt;a blog post dedicated to various options of databases&lt;/a&gt; in AWS, but when speaking of cloud best practices, it’s also important to mention &lt;em&gt;when&lt;/em&gt; should you use what.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relational DBs
&lt;/h3&gt;

&lt;p&gt;Visually, they look similar to excel sheets: Tables, rows, columns, and fields. You interact with your DB using SQL = &lt;strong&gt;Structured Query Language.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just a reminder, AWS’s Relational DB is &lt;strong&gt;&lt;em&gt;Amazon RDS&lt;/em&gt;&lt;/strong&gt;(Relational Database Service), and it lets you choose between 6 different DB services, one of them being Amazon’s &lt;strong&gt;&lt;em&gt;Aurora&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You should NOT use relational DB when there is no need for joins or complex transactions. For example, If I plan on pulling information per customer (like latest purchases of X), or if the fields between entries are prone to vary (entries have some different fields), non-relational DB is the way to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-Relational DBs
&lt;/h3&gt;

&lt;p&gt;Non-relational DBs, such as AWS’s &lt;strong&gt;&lt;em&gt;dynamo DB&lt;/em&gt;&lt;/strong&gt;, are using NoSQL. Fun fact, NoSQL actually means “&lt;strong&gt;not only&lt;/strong&gt; SQL”, and not “&lt;strong&gt;no&lt;/strong&gt; SQL”.&lt;/p&gt;

&lt;p&gt;The difference from relational DB is that the columns in the table can vary, and this will not affect other rows in the DB.&lt;/p&gt;

&lt;p&gt;You should NOT use non-relational DB when the work requires joins or complex transactions. By that, I mean queries like “Get the concatenation of fields A and B for every customer whose last name starts with C and purchased in the last D days.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Warehouse
&lt;/h3&gt;

&lt;p&gt;Amazon’s data warehouse is called &lt;strong&gt;&lt;em&gt;Redshift&lt;/em&gt;&lt;/strong&gt;. &lt;strong&gt;&lt;em&gt;A data warehouse&lt;/em&gt;&lt;/strong&gt; is an information system that stores historical and collective data from single or multiple sources. While the database is designed to &lt;strong&gt;record&lt;/strong&gt; data, the data warehouse is designed to &lt;strong&gt;analyze&lt;/strong&gt; data.&lt;/p&gt;

&lt;p&gt;Now, to reach our anti-pattern, we need to introduce two more terms: OLTP vs OLAP. &lt;strong&gt;&lt;em&gt;OLTP&lt;/em&gt;&lt;/strong&gt; (OnLine Transaction Processing) pulls the entire row as RDS does, and &lt;strong&gt;&lt;em&gt;OLAP&lt;/em&gt;&lt;/strong&gt; (OnLine Analytic Processing) pulls in large numbers of records.&lt;/p&gt;

&lt;p&gt;You should NOT use the data warehouse when you want to perform OLTP queries! You should also NOT use a data warehouse when you can use a data lake. What is a data lake? I’m glad you asked!&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Lake
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A data lake&lt;/em&gt;&lt;/strong&gt; is an architectural approach that allows you to store massive amounts of data in a central location so that it’s readily available to be analyzed. Since data can be stored as-is, you do not have to convert it to a predefined schema, and you no longer need to know what questions to ask about your data beforehand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data lake vs data warehouse&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Reason for storing data: undefined vs pre-defined.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data is left raw until needed vs processed and ready to be queried.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Amazon S3&lt;/em&gt;&lt;/strong&gt; is a great place to create data lakes, you can use &lt;strong&gt;&lt;em&gt;Amazon Athena&lt;/em&gt;&lt;/strong&gt; (serverless interactive query service) and run SQL queries on your data.&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%2Fubzjq9knpbcwaa4vdzsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubzjq9knpbcwaa4vdzsz.png" alt="meme" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Removing Single Points of Failure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A single point of failure&lt;/em&gt;&lt;/strong&gt; refers to one fault or malfunction that can cause an entire system to stop operating. Hopefully, nothing will ever break, but that’s not realistic. The approach here is that when something does break, it does not shut down the whole system.&lt;/p&gt;

&lt;p&gt;Here are a few ways to prevent a single point of failure in AWS:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Introducing Redundancy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Redundancy&lt;/em&gt; is a system design in which a component is duplicated so if it fails there will be a backup. A good basic example of it is in the AWS infrastructure — Availability Zones!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Availability zones&lt;/em&gt; (AZs) consist of one or more discrete data centers, each AZ with its own power, networking, and connectivity, and is housed in separate facilities. Each region has multiple AZs, so if something happens to one of them, it’s far enough from the rest, and they can be used as a backup!&lt;/p&gt;

&lt;h3&gt;
  
  
  Detect Failure
&lt;/h3&gt;

&lt;p&gt;Introduce a mechanism to detect failure. For example, health checks! Network Load Balancers use active and passive &lt;strong&gt;health checks&lt;/strong&gt; to determine whether a target is available to handle requests. That way you can respond to the event earlier before there is any major impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durable Data Storage
&lt;/h3&gt;

&lt;p&gt;You should always aspire for durable data storage. Amazon S3, for example, is designed to provide 99.999999999% durability of objects over a given year.&lt;/p&gt;

&lt;p&gt;99.999999999% (11 nines) durability means that if you store 10,000 objects, on average you may lose one of them every 10 million years or so. Impressive!&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Multi Datacenter Resilience
&lt;/h3&gt;

&lt;p&gt;This one is on the list but is a sub-category of the previous one mentioned. S3 achieves its durability by automating backups creation, which is transparent to the user. It’s not the users who made sure to have 3 backups, they just know that he can always (well, almost) reach their data.&lt;/p&gt;

&lt;p&gt;S3 is not a DB, it is a simple storage system (kind of like Google Drive). A more specific example of automated multi-data center resilience is Amazon Aurora. This is Amazon’s in-house DB service, and it stores copies of the data in a DB cluster across multiple Availability Zones in a single AWS Region.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fault Isolation
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Fault Isolation&lt;/em&gt; means isolating the component, device, or software module causing the error. This topic connects beautifully to the scaling and high availability we talked about prior! More specifically, horizontal scaling — adding more instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharding
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Sharding&lt;/em&gt;, also known as &lt;em&gt;horizontal partitioning&lt;/em&gt;, is a popular scale-out approach for relational databases. Amazon RDS provides great features to make sharding easy to use in the cloud.&lt;/p&gt;

&lt;p&gt;Sharding increases redundancy both because each shard is a replica set, and because even if an entire shard becomes unavailable, the database as a whole still remains partially functional, with part of the schema on different shards.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimizing Costs
&lt;/h2&gt;

&lt;p&gt;There is a dedicated blog post for &lt;a href="https://cupofcode.blog/intro-to-aws-billing-and-pricing/" rel="noopener noreferrer"&gt;Billing &amp;amp; Pricing in AWS&lt;/a&gt;, but here we’ll emphasize the best practices for optimizing costs:&lt;/p&gt;

&lt;h3&gt;
  
  
  Right-Sizing
&lt;/h3&gt;

&lt;p&gt;Right-sizing is the most effective way to control cloud costs. Examples of size options in AWS services are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EC2 instance types, various combinations of CPU, memory, and network capacity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;S3 storage classes, depending on the frequency of access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RDS instance type, also various combinations of CPU, memory, and network capacity&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Elasticity
&lt;/h3&gt;

&lt;p&gt;Elasticity is the ability to fit the resources needed to cope with loads dynamically usually in relation to scale out. It sounds very similar to scalability, right? So I went to check the difference!&lt;/p&gt;

&lt;p&gt;Scalability is being capable to expend for a little additional cost, more of a long-term. For example, using a template. That way, the creation of the template itself will require some work, but adding a new instance using that template will be quicker than creating every new instance from scratch.&lt;/p&gt;

&lt;p&gt;Elasticity, on the other hand, is supporting short-term changes, like your stomach getting bigger when you eat, and smaller after a few hours. If you insist on a cloud example, it’s like adding another server to receive requests, for the period when people go Christmas shopping.&lt;/p&gt;

&lt;p&gt;Elasticity optimizes costs because you can grow or shrink capacity for CPU, memory, and storage resources to adapt to the changing demands, without long-term commitments and contracts!&lt;/p&gt;

&lt;h3&gt;
  
  
  Purchasing Options
&lt;/h3&gt;

&lt;p&gt;The third way to optimize costs is by taking advantage of the variety of purchasing options in AWS! For example, Amazon EC2 Instances have four purchasing options: On-Demand Instances, Reserved Instances, Spot Instances, and Savings Plans. Each one is best for different scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using Cache
&lt;/h2&gt;

&lt;p&gt;Caching helps data to be served faster and with lower latency. There are two types of caching: App caching and edge caching.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;App caching&lt;/strong&gt;: Apps can load faster by keeping temporary files such as thumbnails, scripts, and video snippets on your phone instead of loading them from the web each time. In AWS, &lt;strong&gt;&lt;em&gt;Elasticache&lt;/em&gt;&lt;/strong&gt; is a web service that makes it easy to deploy, operate, and scale an &lt;strong&gt;in-memory cache&lt;/strong&gt; in the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edge caching&lt;/strong&gt;: This is the practice of using intermediate storage between data centers and end users accessing the resource. In AWS, &lt;strong&gt;&lt;em&gt;Amazon CloudFront&lt;/em&gt;&lt;/strong&gt; is a fast &lt;em&gt;content delivery network&lt;/em&gt; (CDN) service that securely delivers content to customers globally with low latency and high transfer speeds.&lt;/p&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%2Fpx5s5v2wvy49f42oluhy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx5s5v2wvy49f42oluhy.png" alt="A reminder about CDN" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Best Practices
&lt;/h2&gt;

&lt;p&gt;Here we will touch on security in a nutshell, and I recommend reading &lt;a href="https://cupofcode.blog/introduction-to-aws-security/" rel="noopener noreferrer"&gt;my AWS security dedicated blog post&lt;/a&gt; for more coverage. Let’s go over AWS’s security best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use AWS features for defense-in-depth&lt;/strong&gt;: &lt;strong&gt;&lt;em&gt;Defense in depth&lt;/em&gt;&lt;/strong&gt; is a strategy that leverages multiple security measures to protect an organization’s assets. The thinking is that if one line of defense is compromised, additional layers exist as a backup to ensure that threats are stopped along the way. AWS provides security features including IAM, firewalls (WAF), port filtering (security groups), and network protection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Share security responsibility with AWS&lt;/strong&gt;: Security and Compliance are a shared responsibility between AWS and the customer. this differentiation of responsibility is commonly referred to as Security &lt;strong&gt;&lt;em&gt;of&lt;/em&gt;&lt;/strong&gt; the Cloud versus Security &lt;strong&gt;&lt;em&gt;in&lt;/em&gt;&lt;/strong&gt; the Cloud. The best practice will be for the customer to do their part.&lt;/p&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%2Fcdn-images-1.medium.com%2Fmax%2F2048%2F0%2A7oN0D_7mw32IMzd-.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%2Fcdn-images-1.medium.com%2Fmax%2F2048%2F0%2A7oN0D_7mw32IMzd-.jpg" alt="[*https://aws.amazon.com/compliance/shared-responsibility-model/](https://aws.amazon.com/compliance/shared-responsibility-model/)*" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduce privileged access&lt;/strong&gt;: Users need to get only the access they &lt;strong&gt;need&lt;/strong&gt;. Every user, role, or group shouldn’t have full access to resources and actions, just enough privileges in order to do their job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security as code&lt;/strong&gt;: Codify your security by creating a golden environment, for example, an EC2 launch template with security patches. That way, every new EC2 instance will be created from that template and therefore be more secure than a basic EC2 instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time auditing&lt;/strong&gt;: An audit gives you an opportunity to remove unneeded IAM users, roles, groups, and policies, and to make sure that your users and software have only the permissions that are required. Examples of auditing services in AWS are &lt;em&gt;AWS inspector&lt;/em&gt; and &lt;em&gt;CloudTrail.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That’s it! Today we talked about best practices in the cloud, and this was the last blog post in the &lt;em&gt;introduction to AWS&lt;/em&gt; series.&lt;/p&gt;

&lt;p&gt;Now with this one behind us, all that’s left is to practice and take the AWS cloud practitioner exam. Best of luck to all of us! :D&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%2Fxvhly0erhvtb14hup035.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%2Fxvhly0erhvtb14hup035.jpg" alt="meme" width="645" height="657"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you read this, you’ve reached the end of the blog post, and I would love to hear your thoughts! Here are the ways to contact me:&lt;br&gt;
Facebook: &lt;a href="https://www.facebook.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.facebook.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Instagram: &lt;a href="https://www.instagram.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.instagram.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Email: &lt;a href="mailto:cupofcode.blog@gmail.com"&gt;cupofcode.blog@gmail.com&lt;/a&gt;&lt;/p&gt;

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

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>beginners</category>
    </item>
    <item>
      <title>My Relocation Diaries — The 2-Years Mark!</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Wed, 09 Mar 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/cupofcode/my-relocation-diaries-the-2-years-mark-67</link>
      <guid>https://forem.com/cupofcode/my-relocation-diaries-the-2-years-mark-67</guid>
      <description>&lt;h3&gt;
  
  
  When does it stop being “a relocation” and start being “the new home”?
&lt;/h3&gt;




&lt;p&gt;As you can understand from the title — I am now celebrating my 2 year anniversary in Dublin, and soon enough so will Covid-19. The difference between 2020 and 2021 is that this year we got vaccines and looser restrictions every once in a while. Furthermore— and this is a big one — this year I finally went to visit home, and that was a meaningful experience.&lt;/p&gt;

&lt;p&gt;Before we dive into my Instagram-beautiful experiences, let’s put things in perspective: After a certain period of time, it stops being an exotic relocation, and it becomes.. a location. Work, relationships, good days, bad days… it’s just life, with a bit of a different spice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I’m not here to sell how everyone should relocate&lt;/em&gt;&lt;/strong&gt;. It’s not a journey that everyone needs to go through. Some don’t want it, and some don’t have the privilege to do it, and it’s ok! For those of you who are curious about the experience, and for those of you who are curious about &lt;strong&gt;my&lt;/strong&gt; experience — here are my thoughts.&lt;/p&gt;




&lt;p&gt;If you read my previous relocation journeys, you know this took a while, but I finally started to feel more comfortable at work, and things eventually clicked. I should’ve seen it coming because I felt the exact same way after the exact same time elapsed in my previous job as well.&lt;/p&gt;

&lt;p&gt;For those of you who don’t know, I am a software developer who works at Amazon’s Payments Security department. This huge department’s job is to make sure your details are stored correctly and securely. To be honest, it is pretty amazing how those multiple teams work together… Anyway, back on track:&lt;/p&gt;

&lt;p&gt;My team’s job is networking. By networking, I mean the foundation of the various components of this big complicated system. This is a crucial part of security, and something I didn’t have any experience in.&lt;/p&gt;

&lt;p&gt;Starting to work in networking is kind of like being an actor in &lt;a href="https://www.imdb.com/title/tt0413573/" rel="noopener noreferrer"&gt;Grey’s anatomy&lt;/a&gt;: You are not a doctor, never intended to be, but your job is to memorize all those complicated lines and look like you know what is &lt;strong&gt;&lt;em&gt;Hemopneumothorax&lt;/em&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;em&gt;Pancreaticoduodenectomy&lt;/em&gt;&lt;/strong&gt; (&lt;a href="https://greysanatomy.fandom.com/wiki/Medical_Glossary" rel="noopener noreferrer"&gt;source&lt;/a&gt;). So, it’s very interesting, but also requires a lot of learning.&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%2F4fjlbquxd4j5fdz38fsg.jpeg" 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%2F4fjlbquxd4j5fdz38fsg.jpeg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Dublin is full of ex-pats, which means you are surrounded by people who are different from you. There is so much to learn about divergent lives and cultures! I can’t write them all down, but here is a cool example: For those of you who don’t know, there is such thing called &lt;a href="https://abcnews.go.com/Lifestyle/irish-goodbye-ghosting-frowned-etiquette-experts/story?id=22918760#:~:text=A%20slang%20phrase%20rumored%20to,bad%20date%20without%20bidding%20farewell.&amp;amp;text=It%20attributes%20the%20phrase%20to,fled%20their%20homeland%20for%20America." rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;“Irish goodbye”&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;, which is when a person is ducking out of a party or social gathering without bidding farewell. I’ve seen it happen several times, and it looks like someone is just going to the restroom, but then never comes back. This is &lt;em&gt;very&lt;/em&gt; different from the &lt;strong&gt;&lt;em&gt;Israeli goodbye&lt;/em&gt;&lt;/strong&gt;, which is not an official term but it means giving a warm hug to each and every person at the party before you go.&lt;/p&gt;

&lt;p&gt;Speaking of people, I’ve discovered that building a social life, especially outside of the workplace — requires effort. You need to actively seek out friends. Sure, this is a struggle for every person in their late 20s, when there is no social structure with crazy hours like school and military service. When you’re in your home country and want company, even if you just moved to a new city — you can always go visit your friends and family, even if it’s a bit further now. It does not work like that here.&lt;/p&gt;

&lt;p&gt;When you’re in a new country, and you don’t want to be alone on a Friday night, you need to find friends, which means you need to go on dates, and let me tell you — &lt;strong&gt;first dates are awkward, even when they are not romantic&lt;/strong&gt;. When you find someone you like, it will take several embarrassing engagements in order to get to a comfortable level. To be honest, not every week do I have the tolerance for that. When having a night off, sometimes it’s just easier to go meet this one person you already feel comfortable with, rather than go &lt;strong&gt;sweat&lt;/strong&gt; on leveling up a potential friendship.&lt;/p&gt;




&lt;p&gt;This year I moved out of a shared apartment and got my own place for the first time. I do recommend sharing a flat when you just arrive in a new country — it makes the landing softer, you’ll definitely be less lonely, and make fewer mistakes. Some people love being surrounded by friends all day, but I appreciate my &lt;em&gt;me-time&lt;/em&gt; — so it was a natural move.&lt;/p&gt;

&lt;p&gt;A rent lease is one year long, so it’s not a big commitment. This change didn’t shake my core. Deciding to study for a driving license did — way more than you’d assume! This process costs money and takes months, and you don’t do it when you have a visible expiration date for your stay. Do you know what that means?&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%2Fn6ojymshw9lof0bzr7x1.jpeg" 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%2Fn6ojymshw9lof0bzr7x1.jpeg" alt="Here we are driving on the wrong side of the road ;)" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is when I realized what a relocation really is. You think it will be like going on a trip and making a detour to grab a cup of coffee. It will take a bit of extra time but is totally worth it — because of the adventure! In reality, &lt;strong&gt;you don’t go back to the original route you planned for your trip&lt;/strong&gt;. This detour propels in a different direction. I’ve seen it relevant even when one decides to relocate back home. This experience changes your life, and you can never know in advance what will be coming back with you.&lt;/p&gt;

&lt;p&gt;Do you know what else suddenly happened this year? I am not the newbie anymore! There are people who seek &lt;em&gt;me&lt;/em&gt; out and &lt;em&gt;my&lt;/em&gt; advice! The other day, I was on the phone with someone who got an offer to relocate to Dublin. He had so many questions, most of which I couldn’t answer because our situations are different: Martial status, job description, Visa requirements… I told him: &lt;em&gt;“Why not get answers only for the crucial things you need now, for the start, and then, when you arrive and meet your colleagues — ask them? I’m sure they’ll be very helpful”&lt;/em&gt;, and he answered: &lt;strong&gt;&lt;em&gt;”Because I trust only Israelis”&lt;/em&gt;&lt;/strong&gt;. Obviously, there is no reason to be skeptical, but I get why he thinks that. I used to think that as well.&lt;/p&gt;

&lt;p&gt;Before I moved here, when it was just me with my two big suitcases, I was absolutely sure that getting on this plane means I wouldn’t date for 2 whole years. This made sense to me because &lt;strong&gt;&lt;em&gt;I trust only Israelis&lt;/em&gt;&lt;/strong&gt;. Then you arrive and quickly realize people are people, and that fear is just false.&lt;/p&gt;




&lt;p&gt;Lastly, I want to talk about Israel. The Israeli-Palestinian conflict is an unpleasant topic to discuss, both here online and outside in the big world. I have had several attempts in writing here, that eventually did not make it to the final draft. This is what I did choose to put: Relocation to another country means you are going to a more heterogenic environment than you had back home. You are most likely to encounter people with different opinions than yours. Remember that you represent your origin country when you are abroad, wherever you are from, especially when there are not many others next to you.&lt;/p&gt;

&lt;p&gt;Speaking of home, I finally went to Israel for the first time after a year and a half! Apparently, there are two parallel timelines, and coming back feels like I’ve been away only for one month. The problem is, that everyone else did spend their months in &lt;strong&gt;this&lt;/strong&gt; timeline, and I need to catch up. I wasn’t here when it was a slow gradual change. Friends are living in different locations and have different priorities. And by the way, good luck trying to convince ANYONE in Tel Aviv to come to meet you anywhere else.&lt;/p&gt;

&lt;p&gt;On one hand — your friendships from your new home don’t have the comfort and ease of the years-long friendships you have left behind. On the other hand — you weren’t there when those friendships shifted and tightened. Eventually, this is a bit of a lose-lose situation, a sacrifice one makes when deciding to move away.&lt;/p&gt;

&lt;p&gt;With that said— I am really enjoying the friendships I was able to maintain. There is something about this deadline, &lt;em&gt;“I’m only here for a month”&lt;/em&gt;, that motivates people to make the effort. Sometimes, when your parents are 2 hours drive, you’ll see them less often than when they are on another continent.&lt;/p&gt;

&lt;p&gt;90% of the people who hear I moved away, say &lt;em&gt;“I will never be able to leave my family”&lt;/em&gt;, which I understand and respect. But after 2 visits of month-long each, I can assure you this method has its benefits as well. Some of my friends, who are in the same country, haven’t met each other as often as they met me!&lt;/p&gt;

&lt;p&gt;When coming home, it is inevitable to think about returning to your country: &lt;em&gt;Would I want to work here? Would I want to live here? What will my evenings look like? What will my weekends look like?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mnq1ty096v36ty4876w.jpeg" 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%2F8mnq1ty096v36ty4876w.jpeg" width="284" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only thing that is irreplaceable is the holidays. All I have is to hope the pandemic will end soon, and I’ll have the luxury to plan vacations in advance. Another problem is that after 2 years in Dublin, &lt;strong&gt;every arrival at the airport means you will be missing someone for a while.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Before we go, I want to dedicate a section to your questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you find a job abroad?&lt;/strong&gt; By looking for a job abroad. Every job description has a location. You just need to be open to the idea, and work on your English ;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the partner doing?&lt;/strong&gt; I did not arrive with a partner, but I did notice there is always one side who looked for a job abroad and one side who came along. Be sure that being the partner is &lt;strong&gt;harder&lt;/strong&gt; than being the initiator of the move, so they deserve extra-kindness! Some of them keep their previous job remotely (which can be very isolating), and some of them find a job as well. The lucky ones find a job in their field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the logistics? How do you actually move?&lt;/strong&gt; The logistics can be a headache. It really depends on your situation and on your destination. &lt;br&gt;
By situation, I mean family, visa, what you left behind, etc. &lt;br&gt;
By destination, I mean things are very different in each country. &lt;br&gt;
If Dublin is your target, I’ll be happy to help!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it hard? Is it lonely?&lt;/strong&gt; It can be sometimes. It’s a fresh start. With that said, you have colleagues and communities and Facebook groups full of people who also want to make friends :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did you manage to make meaningful relationships?&lt;/strong&gt; Yes, I have. I was surprised to discover how two different people from two completely different backgrounds can have such a strong connection and laugh at the same jokes.&lt;/p&gt;




&lt;p&gt;A question I frequently get asked when coming home is &lt;em&gt;“when will you come back for good?”.&lt;/em&gt; Now, that I passed my 2 years mark, I’ve noticed I’m referring to Dublin as &lt;strong&gt;&lt;em&gt;going back home&lt;/em&gt;&lt;/strong&gt; as well, and the only answer I can give is &lt;em&gt;“not now”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0f1ofpq2k6tbqkv1q7q.jpeg" 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%2Fk0f1ofpq2k6tbqkv1q7q.jpeg" width="612" height="408"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you read this, you’ve reached the end of the blog post, and I would love to hear your thoughts! Here are the ways to contact me:&lt;br&gt;
Facebook: &lt;a href="https://www.facebook.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.facebook.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Instagram: &lt;a href="https://www.instagram.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.instagram.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Email: &lt;a href="mailto:cupofcode.blog@gmail.com"&gt;cupofcode.blog@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3170%2F0%2AdFpSvsmM6cEoz9T_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3170%2F0%2AdFpSvsmM6cEoz9T_.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introduction to AWS — Billing &amp; Pricing</title>
      <dc:creator>Cup of Code</dc:creator>
      <pubDate>Fri, 31 Dec 2021 00:00:00 +0000</pubDate>
      <link>https://forem.com/cupofcode/introduction-to-aws-billing-pricing-25bf</link>
      <guid>https://forem.com/cupofcode/introduction-to-aws-billing-pricing-25bf</guid>
      <description>&lt;h3&gt;
  
  
  All you need to know about pricing for AWS services and AWS services for billing
&lt;/h3&gt;

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

&lt;p&gt;Welcome to the 6th blog post in the AWS series! This time we will talk about money, and it’s an important topic &lt;a href="https://aws.amazon.com/certification/certified-cloud-practitioner/" rel="noopener noreferrer"&gt;in the Cloud Practitioner exam&lt;/a&gt;! So what will we talk about today? Here is a table of content for easy access:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;AWS Pricing Introduction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;4 Key Principles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The AWS Free Tier&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Different Support Levels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pricing for individual services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS services that are related to billing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resource Groups &amp;amp; Tagging&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shall we begin?&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%2Fyv1d95h31vbxm78xkhbb.jpeg" 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%2Fyv1d95h31vbxm78xkhbb.jpeg" width="648" height="648"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;h3&gt;
  
  
  AWS Pricing Introduction
&lt;/h3&gt;

&lt;p&gt;Every AWS topic has a whitepaper, and the link to the pricing whitepaper can be found &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/how-aws-pricing-works/how-aws-pricing-works.pdf" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before we dive into the pricings in AWS, let’s recap two fundamental terms we got introduced to in &lt;a href="https://towardsaws.com/introduction-to-aws-cloud-computing-and-global-infrastructure-f73b349d78ce" rel="noopener noreferrer"&gt;the first blog post&lt;/a&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  Different Pricing Models: &lt;strong&gt;&lt;em&gt;CAPEX&lt;/em&gt;&lt;/strong&gt; vs &lt;strong&gt;&lt;em&gt;OPEX&lt;/em&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;CAPEX&lt;/em&gt;&lt;/strong&gt; stands for &lt;strong&gt;Capital Expenditure&lt;/strong&gt;, which is where you pay upfront. It’s a fixed, sunk cost because you need to accurately predict how much resources you are going to need, and it doesn’t give you flexibility for peaks and lows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;OPEX&lt;/em&gt;&lt;/strong&gt; stands for &lt;strong&gt;Operational Expenditure&lt;/strong&gt; which is where you pay for what you use, like electricity bill. Some of you would think it’s also like a water bill but &lt;a href="https://www.moneyguideireland.com/water-charges-2017-new-rules.html" rel="noopener noreferrer"&gt;here in Ireland&lt;/a&gt;, there is no such thing ;)&lt;/p&gt;

&lt;p&gt;For example — Do you need 100 servers for daily traffic and another 50 for the holiday season? With &lt;em&gt;CAPEX&lt;/em&gt;, your only way to support that need is to rent 150 servers for the whole year. &lt;em&gt;OPEX&lt;/em&gt;, on the other hand, gives you flexibility.&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%2F58aektmfrwpdxdo2117z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58aektmfrwpdxdo2117z.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we established that &lt;em&gt;Operational Expenditure&lt;/em&gt; is way better for us, let’s dive into what AWS has to offer.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Pricing Philosophy of AWS
&lt;/h4&gt;

&lt;p&gt;Here is a quote from the whitepaper:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“While the number and types of services offered by AWS have increased dramatically, &lt;strong&gt;our philosophy on pricing has not changed. You pay as you go, pay for what you use, pay less as you use more, and pay even less when you reserve capacity.&lt;/strong&gt;”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s talk about them a bit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pay as you go —in contradiction to paying in advance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pay for what you use — and not a pre-defined fixed price.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pay less as you use more — (per unit), like when a pack of 6 bottles of 1.5L coke is cheaper &lt;strong&gt;per bottle&lt;/strong&gt; than purchasing a single 1.5L&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pay less when you reserve capacity — Like with EC2 instances, for example.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speaking of EC2 instances, here is a quick recap of the EC2 Pricing Models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;On-demand:&lt;/strong&gt; This allows you to pay a fixed rate by the hour (or by the second) with no commitment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reserved:&lt;/strong&gt; Provides you with a capacity reservation, and offers a significant discount on the hourly charge for an instance (like booking a hotel room a year in advance). Contract terms are 1 year or 3 years terms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spot:&lt;/strong&gt; Enables you to bid whatever price you want for instance capacity, providing for even greater savings if your applications have flexible start and end times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dedicated hosts:&lt;/strong&gt; A physical EC2 server dedicated for your use. Dedicated hosts can help you reduce costs by allowing you to use your existing server-bound software licenses.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2AHnjSOaqZ6jw689gDUsEwhw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2AHnjSOaqZ6jw689gDUsEwhw.png" alt="[Introduction to AWS: EC2, related services, and AWS through the CLI](https://medium.com/geekculture/introduction-to-aws-ec2-related-services-and-cli-cfbae53c3409)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will be useful when you’ll get asked &lt;em&gt;“Which of the following is an EC2 pricing option?”&lt;/em&gt;&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Key Principles
&lt;/h3&gt;

&lt;p&gt;While pricing models vary across services, it’s worthwhile to review the 4 key principles and best practices that are broadly applicable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Understand the fundamentals of pricing:&lt;/strong&gt; There are three fundamental drivers of cost with AWS: Compute, Storage, and Data &lt;strong&gt;outbound&lt;/strong&gt; (data going out). Remember that when you get asked &lt;em&gt;“Which of the following is not a fundamental AWS charge?” *— The answer is *&lt;/em&gt;&lt;em&gt;Data Inbound&lt;/em&gt;**.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Start early with cost optimization:&lt;/strong&gt; Whether you started in the cloud, or you are just starting your migration journey to the cloud, AWS has a set of solutions to help you manage and optimize your spending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Maximize the power of flexibility:&lt;/strong&gt; One of the key advantages of cloud-based resources is that you don’t pay for them when they’re not running. By turning off instances you don’t use. you can reduce costs by 70% or more compared to using them 24/7. You can &lt;strong&gt;choose&lt;/strong&gt; and pay for exactly what you need — no minimum commitments or long-term contracts are required unless you &lt;strong&gt;choose&lt;/strong&gt; to save money through a &lt;strong&gt;reservation&lt;/strong&gt; model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Use the right pricing model for the job:&lt;/strong&gt; AWS offers several pricing models depending on the product. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-demand&lt;/strong&gt; instances: you pay for compute or database capacity by the hour or second &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saving Plans:&lt;/strong&gt; a flexible pricing model that offers low prices in exchange for a commitment to a consistent amount of usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot Instances:&lt;/strong&gt; in EC2, which we learned about &lt;a href="https://medium.com/geekculture/introduction-to-aws-ec2-related-services-and-cli-cfbae53c3409" rel="noopener noreferrer"&gt;in a previous blog post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reservations:&lt;/strong&gt; which grants you up to 75% discount.&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%2Fuqooln9ejogs3vh9yklo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqooln9ejogs3vh9yklo.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The AWS Free Tier
&lt;/h3&gt;

&lt;p&gt;The AWS Free Tier enables you to gain free, hands-on experience with more than 60 products on the AWS platform. Why give services free? To attract new customers, of course! And more officially: To help new AWS customers get started in the cloud.&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%2Fzqwmvi7aezikprzkp4az.jpeg" 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%2Fzqwmvi7aezikprzkp4az.jpeg" width="259" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Free Tier includes the following free offer types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;12 Months Free&lt;/strong&gt; —12 months following your initial sign-up date to AWS. When your 12-month free usage term expires, or if your application use exceeds the tier, you simply pay standard, pay-as-you-go service rates. &lt;br&gt;
→ Under this category, you can find (to some extend*) EC2, S3, RDS, and CloudFront.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always Free&lt;/strong&gt; — These free tier offers do not expire and are available to all AWS customers. &lt;br&gt;
→ Under this category, you can find (to some extend*) DynamoDB, S3 Glacier, and Lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trials&lt;/strong&gt; — This tier’s offers are short-term free trials starting from the date you activate a particular service. Once the trial period expires, you simply pay standard, pay-as-you-go service rates. &lt;br&gt;
→ Under this category, you can find (to some extend*) Redshift and GuardDuty.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This section lists some of the most commonly used AWS Free Tier services. The full list of AWS Free Tier services can be found &lt;a href="https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When I write *&lt;/em&gt;&lt;em&gt;(to some extend&lt;/em&gt;)*** it means for the first &lt;em&gt;X hours&lt;/em&gt; / &lt;em&gt;Y GB&lt;/em&gt; or &lt;em&gt;Z requests&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Remember when we talked about account types &lt;a href="https://towardsaws.com/introduction-to-aws-cloud-computing-and-global-infrastructure-f73b349d78ce" rel="noopener noreferrer"&gt;in the first blog post&lt;/a&gt;, under &lt;em&gt;Available Regions&lt;/em&gt;? So, the &lt;strong&gt;AWS Free Tier is not available&lt;/strong&gt; in the AWS GovCloud (US) Regions or the China (Beijing) Region at this time. The Lambda Free Tier is available in the AWS GovCloud (US) Region.&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%2F42xtz2d7589xtm6175r7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42xtz2d7589xtm6175r7.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Speaking of free, remember those for when you’ll get asked &lt;em&gt;“Which of the following AWS services are free? Choose 3”&lt;/em&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Please keep in mind that with VPC, Elastic Beanstalk, CloudFormation, and Auto-Scaling, the underlying provisioned resources will incur charges.&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%2Fnzzyyricpjno89bn6rk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzzyyricpjno89bn6rk7.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Different Support Levels
&lt;/h3&gt;

&lt;p&gt;How many support levels are there? Depends on who you ask.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the course I took, they mention four support levels: &lt;strong&gt;Basic&lt;/strong&gt;, Developer, Business, and Enterprise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;According to &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/how-aws-pricing-works/aws-support-plan-pricing.html" rel="noopener noreferrer"&gt;the pricing whitepaper&lt;/a&gt;, there are 3 support levels: Developer, Business, and Enterprise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In that whitepaper, they send you to a page called &lt;a href="https://aws.amazon.com/premiumsupport/pricing/" rel="noopener noreferrer"&gt;AWS Support Plan Pricing&lt;/a&gt;, where they introduce four support levels: Developer, Business, &lt;strong&gt;Enterprise On-Ramp&lt;/strong&gt;, and Enterprise.&lt;/p&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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2APc0JEuAW9K7xBQcyiPmD5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F1%2APc0JEuAW9K7xBQcyiPmD5w.png" alt="[AWS Support Plan Pricing](https://aws.amazon.com/premiumsupport/pricing/)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I understand why “Basic” isn’t technically a support plan (and not counting it fits well with my &lt;a href="https://en.wikipedia.org/wiki/Charmander" rel="noopener noreferrer"&gt;Pokèmon reference&lt;/a&gt;), and Also — there is a good chance &lt;strong&gt;Enterprise On-Ramp&lt;/strong&gt; is too recent to be asked about in the exam — so I’m not going to talk about this one, but you should remember those names, because you can be asked &lt;em&gt;“Which of the following is an AWS plan?” *and they give made-up options like *“Individual”&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  What Is The Difference?
&lt;/h4&gt;

&lt;p&gt;You will soon see a beautiful table with the contrast between the available support levels, but to summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic&lt;/strong&gt; — Free, but that’s the only good thing about it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt; — Start with $29, and has support that an individual developer needs: Case opening with support, support only on business hours — nothing urgent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Business&lt;/strong&gt; — Start with $100, and they need more availability — tech support 24x7 through chat and phone as well as email, and the ability for anyone in the account to open cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enterprise&lt;/strong&gt; — Start with $15,000, &lt;strong&gt;very&lt;/strong&gt; expensive, compared to the Business level, which looks similar but has 2 main differences: 15 minutes response (that’s fast, you gotta admit) for &lt;strong&gt;business-critical systems down&lt;/strong&gt;, and a 1:1 with a &lt;strong&gt;&lt;em&gt;designated Technical Account Manager (TAM)&lt;/em&gt;&lt;/strong&gt; to proactively monitor your environment and assist with optimization and coordinate access to programs and AWS experts. Fancy.&lt;/p&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%2Fhxq8uv21e1w61rjj1ys1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxq8uv21e1w61rjj1ys1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;All the costs are &lt;em&gt;monthly&lt;/em&gt; and &lt;strong&gt;start&lt;/strong&gt; at the price mentioned, but scale based on the usage! Also, not mentioned in the course but is mentioned in the &lt;a href="https://aws.amazon.com/premiumsupport/pricing/" rel="noopener noreferrer"&gt;AWS Support Plan Pricing&lt;/a&gt; —* “Greater of $5,500.00 &lt;strong&gt;- or -&lt;/strong&gt; 10% of monthly AWS charges”.* Could be too new to be asked about — but worth mentioning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The response times under the &lt;strong&gt;&lt;em&gt;Developer&lt;/em&gt;&lt;/strong&gt; level (24b, 12b) are in business hours — so 24 hours is actually 3 days, not 1.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions on this topic are tricky: &lt;em&gt;“Which of the following AWS Support Plans provide Enhanced Technical Support ONLY during business hours via email?”.&lt;/em&gt; The answer is — &lt;em&gt;Developer&lt;/em&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pricing Details for Individual Services
&lt;/h3&gt;

&lt;p&gt;This section is tricky as well! It requires memorizing (and common sense) because questions in this section go something like this: *“Which of the following components are billed for Amazon RDS instances?”. *Don’t worry, When mentioning a service, I’ll give a reminder of what it is!&lt;/p&gt;

&lt;p&gt;Let’s start with EC2: &lt;em&gt;Elastic Compute Cloud (EC2)&lt;/em&gt; is a virtual server in the cloud.&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%2Fx4bd67llddxf34alf9li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4bd67llddxf34alf9li.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*EIP = elastic IP.&lt;/p&gt;

&lt;p&gt;But hey, who says you even need a server? There are also serverless services in AWS, like lambda and S3. Let’s look at how they are priced:&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%2Fbz5lz687q91tl18q12yd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbz5lz687q91tl18q12yd.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And also:&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%2F26biw0t0wnx9b8cnleip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26biw0t0wnx9b8cnleip.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don’t remember what is EBS? S3? Here is a quick reminder:&lt;/p&gt;

&lt;p&gt;EBS:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EBS is a virtual disk in the cloud. Inside the EC2 service, you will see a tab saying Elastic Block Store. EBS allows you to create storage volumes and attach them to EC2 instances. Once attached, you can create a file system on top of these volumes, run a DB, or use them in any other way you would use a block service. EBS volumes are placed in a specific AZ, where they are &lt;strong&gt;automatically replicated&lt;/strong&gt; to protect you from the failure of a single component.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/geekculture/introduction-to-aws-ec2-related-services-and-cli-cfbae53c3409" rel="noopener noreferrer"&gt;Introduction to AWS: EC2, related services, and AWS through the CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;CloudFront:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Amazon CloudFront&lt;/strong&gt; is a fast content delivery network (CDN) service that securely delivers content to customers globally with low latency, high transfer speeds, all within a developer-friendly environment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cupofcode.medium.com/introduction-to-aws-iam-cloudfront-and-s3-19d1dfd45ae0" rel="noopener noreferrer"&gt;Introduction to AWS: IAM, CloudFront, and S3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;S3, S3 Glacier:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Amazon S3&lt;/em&gt;&lt;/strong&gt; is a &lt;strong&gt;S&lt;/strong&gt;imple &lt;strong&gt;S&lt;/strong&gt;torage &lt;strong&gt;S&lt;/strong&gt;ervice that stores and retrieves &lt;strong&gt;any amount&lt;/strong&gt; of data from anywhere on the web. &lt;strong&gt;&lt;em&gt;S3 Glacier&lt;/em&gt;&lt;/strong&gt; is used for archiving solutions. Here are some more details about its pricing:&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%2Faph8jk91g59twhc3vx4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faph8jk91g59twhc3vx4a.png" width="667" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Snowball pricing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Snowball&lt;/em&gt;&lt;/strong&gt; is A &lt;strong&gt;PB-scale&lt;/strong&gt; data transport solution. PB stands for &lt;strong&gt;&lt;em&gt;petabyte&lt;/em&gt;&lt;/strong&gt;, which is equal to 2 to the 50th power of bytes! So, &lt;strong&gt;&lt;em&gt;Snowball&lt;/em&gt;&lt;/strong&gt; is like a giant USB key.&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%2Fym5gao20l23u6pvpp9jk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym5gao20l23u6pvpp9jk.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see what we talked about in the key principles — Data &lt;strong&gt;inbound&lt;/strong&gt; is free, data &lt;strong&gt;outbound&lt;/strong&gt; is not.&lt;/p&gt;

&lt;p&gt;Now we are reaching the Databases section. RDS is the &lt;strong&gt;R&lt;/strong&gt;elational &lt;strong&gt;D&lt;/strong&gt;atabase &lt;strong&gt;S&lt;/strong&gt;ervice, and the non-relational DB is called &lt;strong&gt;&lt;em&gt;DynamoDB&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;RDS pricing:&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%2Fvdh13vxziaouih6zxfoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdh13vxziaouih6zxfoz.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dynamo DB pricing:&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%2F87ujux4t0d4a8l4wn41x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87ujux4t0d4a8l4wn41x.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, before you go to the next section — pop quiz!&lt;br&gt;
Which of the following components are billed for Amazon RDS instances? &lt;br&gt;
Choose 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;DB instance hours&lt;/li&gt;
&lt;li&gt;I/O requests for Amazon RDS magnetic storage&lt;/li&gt;
&lt;li&gt;Data transfer incurred in replicating data between your primary and standby, in a Multi-AZ DB instance deployment&lt;/li&gt;
&lt;li&gt;Standby time&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Ok, I think we earned our right for a meme break!&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%2Fq1tq2bqu4lbllh1pmth7.jpeg" 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%2Fq1tq2bqu4lbllh1pmth7.jpeg" width="640" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we’ll go over the &lt;em&gt;AWS services related to billing&lt;/em&gt; (there are 9 of those!) and then &lt;em&gt;Tagging and Resource Grouping&lt;/em&gt;, and that’s it!&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%2Fus9am17pksaay8srppjo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fus9am17pksaay8srppjo.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Services Related to Billing
&lt;/h3&gt;

&lt;p&gt;In this section we’ll talk about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The difference between &lt;strong&gt;&lt;em&gt;AWS Budgets&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;AWS Cost Explorer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is &lt;strong&gt;&lt;em&gt;AWS organizations&lt;/em&gt;&lt;/strong&gt; and how it integrates with &lt;strong&gt;&lt;em&gt;CloudTrail&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The difference between &lt;strong&gt;&lt;em&gt;AWS QuickStart&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;AWS LandingZone&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is the &lt;strong&gt;&lt;em&gt;AWS Partner Program&lt;/em&gt;&lt;/strong&gt; and how much does it cost?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The two &lt;strong&gt;AWS cost calculators&lt;/strong&gt; and the difference between them&lt;/p&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%2Fkj0c9fhheist6u18k7v8.jpeg" 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%2Fkj0c9fhheist6u18k7v8.jpeg" width="499" height="499"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;h3&gt;
  
  
  AWS Budgets vs AWS Cost Explorer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Budgets&lt;/em&gt;&lt;/strong&gt; gives you the ability to set custom budgets that alert you when your costs or usage exceed (or are forecasted to exceed) your budgeted amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Cost Explorer&lt;/em&gt;&lt;/strong&gt; has an easy-to-use interface that lets you visualize, understand, and manage your AWS costs and usage over time.&lt;/p&gt;

&lt;p&gt;So, AWS Budgets is used to &lt;em&gt;budget *costs **before&lt;/em&gt;* they have been incurred, and AWS Cost Explorer is used to &lt;em&gt;explore *costs **after&lt;/em&gt;* they have been incurred.&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%2F8wjh65mfviz948svr6vm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wjh65mfviz948svr6vm.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Organizations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Organizations&lt;/em&gt;&lt;/strong&gt; is an account management service that enables you to consolidate multiple AWS accounts into an organization that you create and centrally manage.&lt;/p&gt;

&lt;p&gt;To clarify: you create an organization and add multiple AWS accounts into it. This is a &lt;strong&gt;global&lt;/strong&gt; service, and you reach it by clicking on your username-&amp;gt;my organizations. This service is available in two feature sets: &lt;em&gt;Consolidated billing&lt;/em&gt; and &lt;em&gt;All features&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Consolidated Billing
&lt;/h4&gt;

&lt;p&gt;Consolidated billing has the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One bill&lt;/strong&gt; — You get one bill for multiple accounts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy tracking&lt;/strong&gt; — You can track the charges across multiple accounts and download the combined cost and usage data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Volume pricing discount&lt;/strong&gt; — You can combine the usage across all accounts in the organization to share the volume pricing discounts, Reserved Instance discounts, and Savings Plans. This can result in a lower charge for your project, department, or company than with individual standalone accounts. Unused reserved EC2 instances for EC2 are applied across the group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No extra fee&lt;/strong&gt; — Free!&lt;/p&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%2Fudjoizgdibi7dhohawlz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudjoizgdibi7dhohawlz.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  All Features
&lt;/h4&gt;

&lt;p&gt;All features give you full access:&lt;/p&gt;

&lt;p&gt;When you create an organization, enabling all features is the default. With all features enabled, you can use the advanced account management features available in AWS Organizations such as &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_integrate_services_list.html" rel="noopener noreferrer"&gt;integration with supported AWS services&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies.html" rel="noopener noreferrer"&gt;organization management policies&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Policies&lt;/em&gt;&lt;/strong&gt; in AWS Organizations enable you to apply additional types of management to the AWS accounts in your organization.&lt;/p&gt;

&lt;p&gt;When you create organizations and then put AWS accounts behind those org units, you can apply policy either to the ou or the AWS accounts&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%2Fh3bp254ox8t14f8xbx9y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3bp254ox8t14f8xbx9y.png" alt="AWS accounts, ou=organization units, and policies" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Organizations Best Practices
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enable &lt;strong&gt;multi-factor authentication&lt;/strong&gt; on the root account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a strong and &lt;strong&gt;complex password&lt;/strong&gt; on the root account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Paying account&lt;/strong&gt; should be used for billing purposes only. Do not deploy resources into the paying account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Linked accounts&lt;/strong&gt;: 20 linked accounts by default, there is an ability to add more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Billing Alerts&lt;/strong&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When monitoring is enabled on the paying account, the billing data for all linked accounts is included.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can still create billing alerts per individual account.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pop quiz!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;True/False: With Consolidated Billing, the Paying Account can make changes, like altering resource access, to any of the resources owned by a Linked Account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose the features of Consolidated Billing (choose 3):&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Charging is based per VPC&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A single bill is issued containing the charges for all AWS Accounts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple standalone accounts are combined and may reduce your overall bill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Account charges can be tracked individually.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CloudTrail
&lt;/h3&gt;

&lt;p&gt;This one can be easily confused with CloudWatch, so let’s talk about the differences:&lt;/p&gt;

&lt;h4&gt;
  
  
  CloudTrail vs CloudWatch
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CloudWatch monitors performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CloudTrail monitors API calls in the AWS platform: Changes to AWS env, creation of resources, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How to use CloudTrail with AWS Organizations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CloudTrail is on a per account per region basis but can be aggregated into a single bucket belonging to the paying account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CloudTrail can consolidate logs using an S3 bucket:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turn on CloudTrail in paying account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a bucket policy that allows cross-account access.&lt;br&gt;
Turn on CloudTrail in the other accounts and use the bucket in the paying account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A best practice is to use a separate account for logging.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  AWS Quick Start vs AWS Landing Zone
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Quick Start&lt;/em&gt;&lt;/strong&gt; is a way of deploying environments quickly, using CloudFormation templates built by AWS Solutions Architects and AWS Partners. The resources cost money, of course — but this service is free!&lt;br&gt;
&lt;a href="https://aws-quickstart.github.io/" rel="noopener noreferrer"&gt;https://aws-quickstart.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Landing Zone&lt;/em&gt;&lt;/strong&gt; —This allows you to set up a secure, multi-account AWS environment (as opposed to using *AWS Quick Start *which is just for individual accounts). It is based on AWS best practices and starts with four AWS Accounts: AWS Organisations account, Shared Services account, Log Archive account, and Security account.&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%2Fcdn-images-1.medium.com%2Fmax%2F2628%2F1%2AA5D5vXkhEfusehwN_0CXzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2628%2F1%2AA5D5vXkhEfusehwN_0CXzw.png" alt="[https://aws.amazon.com/solutions/implementations/aws-landing-zone/](https://aws.amazon.com/solutions/implementations/aws-landing-zone/)" width="800" height="905"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Landing Zone is a solution that helps customers more quickly set up a secure, multi-account AWS environment based on AWS best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Landing Zone is currently in Long-term Support and will not receive any additional features.&lt;/strong&gt; Customers interested in setting up a new landing zone should check out &lt;a href="https://aws.amazon.com/controltower/" rel="noopener noreferrer"&gt;AWS Control Tower&lt;/a&gt; (but for our purposes, there is no need to dive into what &lt;em&gt;AWS Control Tower&lt;/em&gt; is).&lt;/p&gt;




&lt;h3&gt;
  
  
  AWS Partner Program
&lt;/h3&gt;

&lt;p&gt;The AWS Partner Network (APN) is &lt;strong&gt;a global community of partners&lt;/strong&gt; that help you build, market, and sell your offerings.&lt;/p&gt;

&lt;p&gt;As you validate your offerings with AWS and &lt;strong&gt;&lt;em&gt;pay the APN annual fee of $2500&lt;/em&gt;&lt;/strong&gt;, you can unlock access to differentiation programs, go-to-market resources, funding benefits, and more to gain recognition with customers and grow your business.&lt;/p&gt;

&lt;p&gt;Together, partners and AWS can provide innovative solutions, solve technical challenges, win deals, and deliver value to our mutual customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Types of partners:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Consulting: These partners design, architect, build, migrate and manage customer workloads and applications on AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technology: These partners provide hardware, connectivity services, or software solutions that are either hosted on or integrated with, the AWS Cloud.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Consulting Partners:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partner  |Practitioner Certs|Associate Certs|Prof/Specialty Certs
------------------------------------------------------------------
Select   |        2         |        2      |          2
Advanced |        4         |        4      |          4
Premier  |       10         |       10      |         10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;More information can be found at &lt;a href="https://aws.amazon.com/partners/programs/" rel="noopener noreferrer"&gt;https://aws.amazon.com/partners/programs/&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Different AWS Cost Calculators
&lt;/h3&gt;

&lt;p&gt;AWS helps you to calculate your costs using a couple of different calculators. You won’t find them in the options of the services in the AWS console, but these come up a lot in the exam.&lt;/p&gt;

&lt;p&gt;There are two AWS pricing tools, both have an old name and a new name (just to be sure — remember both names!):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Simple Monthly calculator&lt;/em&gt;&lt;/strong&gt; (aka &lt;strong&gt;&lt;em&gt;AWS Pricing Calculator&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS TCO (Total Cost of Ownership) calculator&lt;/em&gt;&lt;/strong&gt; (aka &lt;strong&gt;&lt;em&gt;Migration Evaluator&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AWS Simple Monthly Calculator&lt;/strong&gt; is used to calculate your running costs on AWS on a per month basis. It is not a comparison tool. &lt;a href="https://docs.aws.amazon.com/pricing-calculator/latest/userguide/what-is-pricing-calculator.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/pricing-calculator/latest/userguide/what-is-pricing-calculator.html&lt;/a&gt; pricing calculator &lt;a href="https://calculator.aws/#/" rel="noopener noreferrer"&gt;https://calculator.aws/#/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS TCO Calculator&lt;/strong&gt; is used to compare the costs of running your infrastructure on-premise vs in the AWS cloud. It will generate reports that you can give to your C-level execs to make a business case to move to the cloud.&lt;/p&gt;

&lt;p&gt;More information can be found &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/how-aws-pricing-works/aws-pricingtco-tools.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Resource Groups &amp;amp; Tagging
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;AWS Tagging&lt;/em&gt;&lt;/strong&gt;: A tag is a label that you or AWS assigns to an AWS resource. Each tag consists of &lt;em&gt;a key&lt;/em&gt; and &lt;em&gt;a value&lt;/em&gt;. Tags are metadata (= data about data) and you can use them to organize your resources, and cost allocation tags to track your AWS costs on a detailed level.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For each resource, each tag key must be unique, and each tag key can have only one value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tags can sometimes be inherited. Tags from a CloudFormation stack are inherited to all the resources.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example: {env: prod}. So, If our CloudFormation stack has this tag — all the resources that are created in that stack will have this tag as well. Also, You can’t have two tags with the key env.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Resource groups&lt;/em&gt;&lt;/strong&gt; make it easy to group your resources using the &lt;strong&gt;tags&lt;/strong&gt; that are assigned to them. You can group resources that share one or more tags. Resource groups contain information such as Region, Name, EmployeeID, Department.&lt;/p&gt;

&lt;p&gt;Tags can contain specific information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For EC2 — Public &amp;amp; Private IP Addresses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For ELB — Port Configurations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For RDS — Database, Engine, etc&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Resource groups&lt;/em&gt; can be found in the AWS console header, next to &lt;em&gt;Services&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Using Resource Groups, you can apply automation to resources tagged with specific tags. For example, stop all EC2 instances in the Ireland region. Resource Groups in combination with &lt;a href="https://aws.amazon.com/systems-manager/" rel="noopener noreferrer"&gt;***AWS Systems Manager&lt;/a&gt;*** allow you to control and execute automation against entire fleets of EC2 instances, all at the push of a button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Tag Editor&lt;/em&gt;&lt;/strong&gt; is a &lt;strong&gt;global&lt;/strong&gt; service that allows us to discover resources and add additional tags to them as well (newer regions may take some time to be compatible with the tag editor). To clarify, the tag editor is good for &lt;strong&gt;finding&lt;/strong&gt; all your tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pop Quiz!&lt;/strong&gt;&lt;br&gt;
Which two of the following options best describe a Resource Group? Choose 2.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A resource group is a collection of resources of the same type (EC2, S3, etc.) that share one or more tags or portions of tags.&lt;/li&gt;
&lt;li&gt;A resource group is a collection of resources that share one or more tags.&lt;/li&gt;
&lt;li&gt;A resource group is a collection of resources of the same type (EC2, S3, etc.) that are deployed in the same Availability Zone.&lt;/li&gt;
&lt;li&gt;A resource group is a collection of resources that are deployed in the same AWS Region, and that match the criteria specified in the group’s query.&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  That’s it!
&lt;/h4&gt;

&lt;p&gt;We’ve learned a lot today! And this time, there is a lot to memorize as well!&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%2Fenztfz49ljzg0earl3c8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenztfz49ljzg0earl3c8.gif" width="480" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, a quick recap on what we talked about today, and then you can test yourself!&lt;/p&gt;

&lt;p&gt;We started with &lt;strong&gt;AWS Pricing Introduction&lt;/strong&gt;, the &lt;strong&gt;4 Key Principles&lt;/strong&gt; and the &lt;strong&gt;AWS Free Tier&lt;/strong&gt;. After that, we got introduced to the &lt;strong&gt;Different Support Levels&lt;/strong&gt; and the differences between them. The main and juicy part of the blog post was the &lt;strong&gt;pricing for individual services&lt;/strong&gt; and the &lt;strong&gt;AWS services that are related to billing&lt;/strong&gt;. Lastly, we got introduced to &lt;strong&gt;Resource Groups &amp;amp; Tagging&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Now, Test Yourself!
&lt;/h4&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%2Fdofyx7zhsi9cz99d0h21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdofyx7zhsi9cz99d0h21.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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




&lt;p&gt;If you read this, you’ve reached the end of the blog post, and I would love to hear your thoughts! Here are the ways to contact me:&lt;br&gt;
Facebook: &lt;a href="https://www.facebook.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.facebook.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Instagram: &lt;a href="https://www.instagram.com/cupofcode.blog/" rel="noopener noreferrer"&gt;https://www.instagram.com/cupofcode.blog/&lt;/a&gt;&lt;br&gt;
Email: &lt;a href="mailto:cupofcode.blog@gmail.com"&gt;cupofcode.blog@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3170%2F0%2AdFpSvsmM6cEoz9T_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3170%2F0%2AdFpSvsmM6cEoz9T_.png" alt="[https://cupofcode.blog/](https://cupofcode.blog/)" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
