<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jannik Wempe</title>
    <description>The latest articles on DEV Community by Jannik Wempe (@jannikwempe).</description>
    <link>https://dev.to/jannikwempe</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%2F219392%2Fcf6e0296-a4fe-4a2a-a123-3b0e13d25c1b.jpeg</url>
      <title>DEV Community: Jannik Wempe</title>
      <link>https://dev.to/jannikwempe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jannikwempe"/>
    <language>en</language>
    <item>
      <title>Keep Your Functions Clean &amp; Focused: Context Provision with Node.js AsyncLocalStorage</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Mon, 31 Mar 2025 05:37:27 +0000</pubDate>
      <link>https://dev.to/jannikwempe/keep-your-functions-clean-focused-context-provision-with-nodejs-asynclocalstorage-1n63</link>
      <guid>https://dev.to/jannikwempe/keep-your-functions-clean-focused-context-provision-with-nodejs-asynclocalstorage-1n63</guid>
      <description>&lt;p&gt;You have probably heard of the &lt;strong&gt;SOLID principles&lt;/strong&gt; in programming. By applying them to your codebase, you can create a solid (🤡) architecture that is both easy to maintain and extend.&lt;/p&gt;

&lt;p&gt;SOLID stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single Responsibility&lt;/li&gt;
&lt;li&gt;Open/Closed&lt;/li&gt;
&lt;li&gt;Liskov Substitution&lt;/li&gt;
&lt;li&gt;Interface Segregation&lt;/li&gt;
&lt;li&gt;Dependency Inversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, I will demonstrate how to leverage Node.js’s built-in &lt;code&gt;AsyncLocalStorage&lt;/code&gt; to implement the &lt;strong&gt;Open/Closed principle&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Open/Closed Principle?
&lt;/h2&gt;

&lt;p&gt;The open/closed principle states that software entities (classes, modules, functions, etc.) should be &lt;strong&gt;open for extension but closed for modification&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what does that mean?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this post, I will focus on two examples to showcase how you can apply the open/closed principle using Node.js’s &lt;code&gt;AsyncLocalStorage&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database transactions&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, I'll show you a violation of the open/closed principle, and then I will demonstrate how to adhere to it using Node.js’s built-in &lt;code&gt;AsyncLocalStorage&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Violation of the Open/Closed Principle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: Database Transactions
&lt;/h3&gt;

&lt;p&gt;Imagine you have a repository function that creates a user profile in the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userProfileTable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, now suppose you want to create the user profile together with a user entity stored in a different table. You should do that in a single transaction because neither should exist without the other. This is what you might do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ we have added the transaction argument to the function (= extended the function)&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ this violates the open/closed principle&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userProfileTable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userTable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works, but it is a violation of the open/closed principle ❌ You are modifying the &lt;code&gt;createUserProfile&lt;/code&gt; function to accommodate the new requirement. Every time you decide to run a repository function within a transaction, you must modify the function to pass the transaction argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Logging
&lt;/h3&gt;

&lt;p&gt;Imagine you have a logger with an &lt;code&gt;appendKeys&lt;/code&gt; method that appends keys to every subsequent log statement (just like the &lt;a href="https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/#temporary-keys" rel="noopener noreferrer"&gt;logger from Powertools for AWS Lambda&lt;/a&gt;). This is very useful to find all logs that are related.&lt;/p&gt;

&lt;p&gt;Consider a scenario where you have a function that processes an order. You want to easily find all logs related to a specific order. To achieve that, you use &lt;code&gt;logger.appendKeys&lt;/code&gt; to append the &lt;code&gt;orderId&lt;/code&gt; to every log statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendKeys&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// calculation...&lt;/span&gt;

  &lt;span class="c1"&gt;// this will also log the orderId since we have appended it to the logger instance&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calculated order total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, suppose you want to process multiple orders concurrently. You cannot simply call &lt;code&gt;appendKeys&lt;/code&gt; on the global logger instance because the &lt;code&gt;orderId&lt;/code&gt; key would be overwritten, as all &lt;code&gt;processOrder&lt;/code&gt; executions share the same logger instance.&lt;/p&gt;

&lt;p&gt;One way to deal with this, would be to create a separate logger instance for every &lt;code&gt;processOrder&lt;/code&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;childLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChild&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;childLogger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ we had to add the logger argument to the function&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ this violates the open/closed principle&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendKeys&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another solution is to not use &lt;code&gt;logger.appendKeys&lt;/code&gt; altogether and instead pass the &lt;code&gt;orderId&lt;/code&gt; to every log statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;childLogger&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ we now pass the whole order because we need access to the orderId for logging&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ this violates the open/closed principle&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// calculation...&lt;/span&gt;

  &lt;span class="c1"&gt;// this will also log the orderId since we have appended it to the logger instance&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calculated order total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is not only tedious but also error-prone, since you must consistently use the same &lt;code&gt;orderId&lt;/code&gt; key in every log statement to correlate logs associated with a specific order.&lt;/p&gt;

&lt;p&gt;You can imagine that both of these approaches quickly become unmanageable—especially when &lt;code&gt;processOrder&lt;/code&gt; calls many other functions in a deeply nested manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying the Open/Closed Principle with AsyncLocalStorage
&lt;/h2&gt;

&lt;p&gt;Now that we know what a violation of the open/closed principle looks like, let's explore how to apply the open/closed principle using Node.js’s built-in &lt;code&gt;AsyncLocalStorage&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context Sharing with AsyncLocalStorage
&lt;/h3&gt;

&lt;p&gt;We want the &lt;code&gt;createUserProfile&lt;/code&gt; and &lt;code&gt;processOrder&lt;/code&gt; functions to work in different scenarios without needing to modify them. For instance, &lt;code&gt;createUserProfile&lt;/code&gt; shouldn't have to know whether it is running in a transaction or not, and &lt;code&gt;processOrder&lt;/code&gt; should not change just because it is executed concurrently.&lt;/p&gt;

&lt;p&gt;So how do we solve this?&lt;/p&gt;

&lt;p&gt;We can leverage &lt;code&gt;AsyncLocalStorage&lt;/code&gt; to create a context that is available to all functions executed within the same scope. This allows us to modify the behavior of functions without actually changing their implementations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AsyncLocalStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:async_hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AsyncLocalStorage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No context available&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;ℹ️ I have learned about the usage of &lt;code&gt;AsyncLocalStorage&lt;/code&gt; from the codebase of &lt;a href="https://github.com/terminaldotshop/terminal/blob/229bbd69d1a2576fbd3302ac8864b87fb4d184ed/packages/core/src/context.ts" rel="noopener noreferrer"&gt;terminaldotshop/terminal&lt;/a&gt;. This is where I took the code for &lt;code&gt;createContext&lt;/code&gt; from and also the transaction example (not the logging example though).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;createContext&lt;/code&gt; creates a private storage &lt;code&gt;context&lt;/code&gt; that is only accessible within the &lt;code&gt;createContext&lt;/code&gt; function. It returns an object with a &lt;code&gt;use&lt;/code&gt; method that returns the current context and a &lt;code&gt;provide&lt;/code&gt; method that allows you to provide a context (&lt;code&gt;value&lt;/code&gt;) that is available with the &lt;code&gt;callback&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Example usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;userContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// this can be somewhere deeply nested in your codebase&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;ℹ️ This is probably familiar to you if you know React. React also has &lt;code&gt;useContext&lt;/code&gt; and &lt;code&gt;createContext&lt;/code&gt; to provide data to (potentially deeply nested) components without having to pass props through every component.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;How can we leverage the &lt;code&gt;createContext&lt;/code&gt; function to solve our examples?&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1 Revisited: Database Transactions
&lt;/h3&gt;

&lt;p&gt;Lets use the &lt;code&gt;createContext&lt;/code&gt; function to create a context for the database transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransactionOrDb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TransactionContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionOrDb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;provideTransaction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;transactionOptions&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;TransactionOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if there already is a transaction context, we use that&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TransactionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise we start a new transaction that we provide to the callback&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;TransactionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;transactionOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useTransactionOrDb&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionOrDb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionOrDb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this throws outside of the context (see `createContext().use()`)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TransactionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if not within a context, we use the db instance&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two functions that essentially wrap the &lt;code&gt;use&lt;/code&gt; and &lt;code&gt;provide&lt;/code&gt; methods of the &lt;code&gt;createContext&lt;/code&gt; function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;provideTransaction&lt;/code&gt; is used to provide a transaction to the callback function. It either uses an already existing transaction or starts a new one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useTransactionOrDb&lt;/code&gt; returns the transaction if it is within a transaction context or the db instance otherwise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, instead of using the &lt;code&gt;db&lt;/code&gt; instance directly in our app, we can just always use &lt;code&gt;useTransactionOrDb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Back to our example, we have the following code before deciding to use a transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useTransactionOrDb&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userProfileTable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;createUserProfile&lt;/code&gt; will just use the &lt;code&gt;db&lt;/code&gt; instance directly since it is not within a transaction context.&lt;/p&gt;

&lt;p&gt;Now, decide that we have to run &lt;code&gt;createUserProfile&lt;/code&gt; in a transaction together with &lt;code&gt;createUser&lt;/code&gt;. We can do that by providing a transaction to the &lt;code&gt;createUserProfile&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;provideTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ the arguments are unchanged – no modification needed&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useTransactionOrDb&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userProfileTable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useTransactionOrDb&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userTable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are following the open/closed principle! We did not have to modify the &lt;code&gt;createUserProfile&lt;/code&gt; function to accommodate the new requirement. Only the caller of the function has to know about the context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2 Revisited: Logging
&lt;/h3&gt;

&lt;p&gt;Now, let's see how we can apply the open/closed principle to the logging example.&lt;/p&gt;

&lt;p&gt;Again, we are creating some helper functions that wrap the &lt;code&gt;use&lt;/code&gt; and &lt;code&gt;provide&lt;/code&gt; methods of the &lt;code&gt;createContext&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LoggerContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createLoggerContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if there is already a LoggerContext, use it&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LoggerContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if there is no LoggerContext, create a new one with a new child logger&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;childLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChild&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LoggerContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;childLogger&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;childLogger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LoggerContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is essentially the same as in the previous example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;createLoggerContext&lt;/code&gt; is used to create a child logger and provide it to the callback function.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getLogger&lt;/code&gt; is used to return the logger. If there is no logger in the context, it will return the global logger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we can use that to follow the open/closed principle in our second example. Instead of using the &lt;code&gt;logger&lt;/code&gt; instance directly, just always use &lt;code&gt;getLogger&lt;/code&gt;. This is how our initial code looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;appendKeys&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// calculation...&lt;/span&gt;

  &lt;span class="c1"&gt;// this will also log the orderId since we have appended it to the logger instance&lt;/span&gt;
  &lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calculated order total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, now we change it to run &lt;code&gt;processOrder&lt;/code&gt; in parallel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createLoggerContext&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ the arguments are unchanged – no modification needed&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;appendKeys&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateOrderTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// calculation...&lt;/span&gt;

  &lt;span class="c1"&gt;// this will also log the orderId since we have appended it to the logger instance&lt;/span&gt;
  &lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calculated order total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now create a child logger for every &lt;code&gt;processOrder&lt;/code&gt; call. &lt;code&gt;processOrder&lt;/code&gt; can just call &lt;code&gt;getLogger().appendKeys&lt;/code&gt; to append the &lt;code&gt;orderId&lt;/code&gt; to the logger as it did before. Now the keys don't overwrite each other because &lt;code&gt;getLogger()&lt;/code&gt; returns different a child logger instances.&lt;/p&gt;

&lt;p&gt;By using &lt;code&gt;getLogger()&lt;/code&gt; everywhere in throughout your codebase instead of the global &lt;code&gt;logger&lt;/code&gt; instance, we don't have to modify any functions if they are run differently. We are following the open/closed principle!&lt;/p&gt;

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

&lt;p&gt;In this post, we have seen how to apply the open/closed principle using Node.js’s built-in &lt;code&gt;AsyncLocalStorage&lt;/code&gt;. We created a &lt;code&gt;createContext&lt;/code&gt; function that allows us to establish a context for any type, which we then leveraged to solve our two examples.&lt;/p&gt;

&lt;p&gt;Here are some key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decoupled Context Management: By using AsyncLocalStorage, you can inject contextual data (like transaction handles or logging metadata) without modifying the core business logic.&lt;/li&gt;
&lt;li&gt;Adherence to the Open/Closed Principle: Functions remain closed for modification while still being open for extension, minimizing the need to alter function signatures as new requirements arise.&lt;/li&gt;
&lt;li&gt;Elimination of Manual Parameter Drag: AsyncLocalStorage prevents the tedious and error-prone process of manually threading context through multiple function calls.&lt;/li&gt;
&lt;li&gt;Improved Maintainability: By isolating context handling from your repository and logging logic, your codebase stays cleaner, more modular, and easier to maintain.&lt;/li&gt;
&lt;li&gt;Enhanced Debugging and Consistency: Automatically propagated context ensures that logs and database transactions are consistent, making debugging and analytics more straightforward.&lt;/li&gt;
&lt;li&gt;Versatile Application: This approach benefits various scenarios, including managing database transactions and setting up consistent logging across parallel asynchronous processes.
These takeaways emphasize how leveraging Node.js AsyncLocalStorage can lead to more robust, maintainable, and scalable code while adhering to fundamental design principles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/api/async_context.html" rel="noopener noreferrer"&gt;Node.js AsyncLocalStorage API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/terminaldotshop/terminal/blob/229bbd69d1a2576fbd3302ac8864b87fb4d184ed/packages/core/src/context.ts" rel="noopener noreferrer"&gt;AsyncLocalStorage Usage in the terminaldotshop codebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle" rel="noopener noreferrer"&gt;Open/Closed Principle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/SOLID_principle" rel="noopener noreferrer"&gt;SOLID Principles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>Create Your Personal, Pay-Per-Use ChatGPT Client in Minutes</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Sun, 30 Apr 2023 09:43:44 +0000</pubDate>
      <link>https://dev.to/jannikwempe/create-your-personal-pay-per-use-chatgpt-client-in-minutes-3pdc</link>
      <guid>https://dev.to/jannikwempe/create-your-personal-pay-per-use-chatgpt-client-in-minutes-3pdc</guid>
      <description>&lt;p&gt;I was hesitant to purchase ChatGPT Pro. While I often use ChatGPT, the $20 per month price tag seemed excessive for my needs. However, the slow response time and limited availability were frustrating. I appreciate the serverless, pay-per-use approach, as that's what I was seeking. Thankfully, &lt;a href="https://github.com/mckaywrigley/chatbot-ui"&gt;Chatbot UI&lt;/a&gt; offers a solution!&lt;/p&gt;

&lt;p&gt;In this article, I'll explain how to host your own ChatGPT UI, allowing you to experience faster response times and pay only for what you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  About ChatBot UI
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Chatbot UI is an open source chat UI for AI models.&lt;br&gt;&lt;br&gt;
– &lt;a href="https://github.com/mckaywrigley/chatbot-ui"&gt;ChatBot UI GitHub Repository&lt;/a&gt;&lt;br&gt;
ChatBot UI is a NextJS app and has an interface that is familiar if you have used ChatGPT:&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;It has features like different chats, saving prompt templates, switching AI models (e.g. &lt;code&gt;gpt-3.5-turbo&lt;/code&gt;) and more. You just provide some configs like your Open API key and are good to go.&lt;/p&gt;

&lt;p&gt;One feature that is missing though is saving chats and prompts to a database. Data is currently stored in local storage. But the APIs are quite nice and you could easily add a database yourself.&lt;/p&gt;

&lt;p&gt;I won't go into details on configuration here. The &lt;a href="https://github.com/mckaywrigley/chatbot-ui/blob/main/README.md"&gt;Readme.md&lt;/a&gt; is the single source of truth and will have the up to date instructions.&lt;/p&gt;

&lt;p&gt;You could leave the &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; environment variable blank and have the user specify the key but I don't want anybody else to use my UI at all. That is why we are now looking into how to protect your ChatBot UI website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding BasicAuth
&lt;/h2&gt;

&lt;p&gt;I am using the most basic way to protect a website: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication"&gt;Basic Auth.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basic auth is a simple HTTP authentication scheme used to protect web resources that require user authentication. It works by sending the combination of a username and password, encoded in base64 format, as plain text over the network (you should use HTTPS). It is good enough for this use case.&lt;/p&gt;

&lt;p&gt;I am deploying my version of the ChatBot you to Vercel. &lt;a href="https://github.com/vercel/examples/tree/main/edge-middleware/basic-auth-password"&gt;There is an example from Vercel on GitHub on how to add Basic Auth&lt;/a&gt;. It all comes down to using an edge middleware and an API route. This is my slightly modified version of the middleware using environment variables to provide values for &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;pwd&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pwd&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASIC_AUTH_USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;pwd&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASIC_AUTH_PASSWORD&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks the provided &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;pwd&lt;/code&gt; if the &lt;code&gt;authorization&lt;/code&gt; header is provided and allows the requests to pass through if it is correct or otherwise rewrites the response to the &lt;code&gt;/api/auth&lt;/code&gt; URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/auth.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WWW-authenticate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Basic realm="Secure Area"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Auth Required.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just setting a header and a &lt;code&gt;401&lt;/code&gt; (Unauthorized) status code.The &lt;code&gt;WWW-authenticate&lt;/code&gt; field is used to initiate the authentication process, and the value &lt;code&gt;Basic realm="Secure Area"&lt;/code&gt; instructs the client to send encoded username and password credentials to access the "Secure Area" of the website.&lt;/p&gt;

&lt;p&gt;You just have to add those two files to your ChatBot UI code and provide the &lt;code&gt;BASIC_AUTH_USER&lt;/code&gt; and &lt;code&gt;BASIC_AUTH_PASSWORD&lt;/code&gt; to add Basic Auth to your own version of ChatGPT.&lt;/p&gt;

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

&lt;p&gt;It is quite straightforward to deploy your own version of ChatGPT with fast response times without paying $20 per month. This can be accomplished in less than 10 minutes, and it also provides a solid starting point for customizing everything to your liking without having to code everything from scratch.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>You Have Fucked Up! How to git revert?</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Wed, 25 Jan 2023 20:12:27 +0000</pubDate>
      <link>https://dev.to/jannikwempe/you-have-fucked-up-how-to-git-revert-515i</link>
      <guid>https://dev.to/jannikwempe/you-have-fucked-up-how-to-git-revert-515i</guid>
      <description>&lt;p&gt;You have messed up production. All hell broke loose. What to do now? Fix it as fast as possible and undo the last change that made everything fall apart to unblock further deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix Production Fast
&lt;/h2&gt;

&lt;p&gt;First of all, it is a good idea to get back to a working version of the application as fast as possible. Many hosting platforms have some sort of rollback functionality (&lt;a href="https://vercel.com/docs/concepts/deployments/instant-rollback" rel="noopener noreferrer"&gt;like Vercel's instant rollback&lt;/a&gt;) that you can use to get back to a working application in seconds. Or you deploy the last working commit from your local machine (be careful to not make things worse; you can leverage &lt;a href="https://git-scm.com/docs/git-bisect" rel="noopener noreferrer"&gt;git bisect&lt;/a&gt; to find the last working commit). This is only a temporary solution. The HEAD of your main branch will still point to the broken application version. So how to fix that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Undo Your Fuckup – Git Revert
&lt;/h2&gt;

&lt;p&gt;This section is about getting your main branch (or trunk or whatever you want to call it) back to a functional state to unblock further deployments (and to prevent yourself from accidentally deploying the broken application again using your CI/CD pipeline).&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove Last Commit (not &lt;code&gt;git revert&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;You could just remove your last commit using &lt;code&gt;git reset HEAD^ --hard&lt;/code&gt; and &lt;code&gt;git push --force&lt;/code&gt; and get rid of that commit (assuming that this has introduced the error). &lt;strong&gt;This is rarely a good idea.&lt;/strong&gt; You don't want to rewrite the git history of a shared remote branch. (Even if you are alone in this project I'd prefer the next solution). It can introduce conflicts with your co-workers' local working version of that branch. But how to do better?&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%2Frx8gexcvj67lpgpxinp0.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%2Frx8gexcvj67lpgpxinp0.png" alt="removing commit SHA1 by using git reset and force push" width="425" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is where&lt;/strong&gt; &lt;code&gt;git revert&lt;/code&gt; &lt;strong&gt;comes into place.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Revert (undo) the Last Commit
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;git revert&lt;/code&gt; is used to record some new commits to &lt;strong&gt;reverse the effect of some earlier commits&lt;/strong&gt; (often only a faulty one).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(Source:&lt;/em&gt; &lt;a href="https://git-scm.com/docs/git-revert" rel="noopener noreferrer"&gt;&lt;em&gt;Git revert documentation&lt;/em&gt;&lt;/a&gt;&lt;em&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is what we want. We create a new commit (not changing the existing commits and messing with our co-workers' sanity) that applies the changes that the commit to be reverted introduced. We can revert the changes of the last commit by finding its SHA using &lt;code&gt;git log&lt;/code&gt; and run &lt;code&gt;git revert SHA&lt;/code&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%2Fd2rtr5yiotnjhsucc3gl.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%2Fd2rtr5yiotnjhsucc3gl.png" alt="reverting a single commit using git log and git revert" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Commit &lt;code&gt;SHA3&lt;/code&gt; now applies the changes that are required to get back from &lt;code&gt;SHA2&lt;/code&gt; to &lt;code&gt;SHA1&lt;/code&gt;. Problem solved and we are good to go, right? Well, in this simple case, yes. But often we work with Pull Requests (PRs) and create merge commits introducing our (shitty) changes to the main branch. Trying the above will result in this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git revert SHA2
&lt;span class="c"&gt;# error: commit SHA2 is a merge but no -m option was given.&lt;/span&gt;
&lt;span class="c"&gt;# fatal: revert failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git revert&lt;/code&gt; fails and you start to sweat. Let's explore what this error is about and how to fix it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Revert a Merge Commit
&lt;/h3&gt;

&lt;p&gt;The part "but no -m option was given" of the previous error is already hinting at the solution. What is the &lt;code&gt;-m&lt;/code&gt; option of &lt;code&gt;git revert&lt;/code&gt;?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;-m parent-number&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--mainline parent-number&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Usually you cannot revert a merge because you do not know &lt;strong&gt;which side of the merge should be considered the mainline&lt;/strong&gt;. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Source: &lt;a href="https://git-scm.com/docs/git-revert#_options" rel="noopener noreferrer"&gt;Git revert documentation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Okay, next question: What does "which side of the merge should be considered the mainline" mean?&lt;/p&gt;

&lt;p&gt;Let's have a look at this scenario:&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%2Fmrpwadcf60tztxts3ut6.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%2Fmrpwadcf60tztxts3ut6.png" alt="two branches and a merge commit SHA3 that is based on SHA1 (main branch) and SHA2 (branch A)" width="398" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, &lt;code&gt;SHA3&lt;/code&gt; which introduced the issues has &lt;strong&gt;two parent branches&lt;/strong&gt; (&lt;code&gt;SHA1&lt;/code&gt; and &lt;code&gt;SHA2&lt;/code&gt;). How should git know which one it should revert to (the mainline)? This is what you have to tell &lt;code&gt;git revert&lt;/code&gt; with the &lt;code&gt;-m&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;How to find the "parent number" that you have to pass to &lt;code&gt;git revert -m&lt;/code&gt;? You can use &lt;code&gt;git catfile -p SHA3&lt;/code&gt; for that or look at the commit message of the merge commit using &lt;code&gt;git log&lt;/code&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%2Frtvcru4pdpwtaa9eevrt.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%2Frtvcru4pdpwtaa9eevrt.png" alt="git log and git cat-file commands in action" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git log&lt;/code&gt; tells you that &lt;code&gt;SHA3&lt;/code&gt; is a merge commit of &lt;code&gt;SHA1&lt;/code&gt; and &lt;code&gt;SHA2&lt;/code&gt;. &lt;code&gt;git cat-file&lt;/code&gt; shows you details for commit &lt;code&gt;SHA3&lt;/code&gt; and tells you that &lt;code&gt;SHA3&lt;/code&gt; has two parents (basically the same info as in the commit message). In both cases the first mentioned SHA is &lt;code&gt;parent-number&lt;/code&gt; 1 (the second 2 etc.). In order to revert the changes introduced in commit &lt;code&gt;SHA3&lt;/code&gt; and get back to &lt;code&gt;SHA1&lt;/code&gt; you have to execute &lt;code&gt;git revert -m 1 SHA3&lt;/code&gt;. Now you are all set and can push the branch with the new commit.&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%2F7s14q40b0umemm8acy3u.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%2F7s14q40b0umemm8acy3u.png" alt="adding a new commit SHA4 that reverts SHA3 back to the state of SHA1" width="427" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you know to revert merge commits and understand what you are doing instead of copying the command from Stackoverflow over and over again 😜&lt;/p&gt;

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

&lt;p&gt;It can become stressful if you are in the situation of having to fix a production problem that you (or a co-worker) have introduced. Things get really bad if you face a "fatal" error trying to revert a merge commit. Make sure you can deal with these situations before you have to deal with them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The time to repair a roof is when the sun is shining.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OR -&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The time to learn how to deal with a production issue is when production is not broken.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>How To Use GitHub Actions for Deployments When Following Trunk-Based Development</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Wed, 14 Dec 2022 06:19:01 +0000</pubDate>
      <link>https://dev.to/jannikwempe/how-to-use-github-actions-for-deployments-when-following-trunk-based-development-4hmf</link>
      <guid>https://dev.to/jannikwempe/how-to-use-github-actions-for-deployments-when-following-trunk-based-development-4hmf</guid>
      <description>&lt;p&gt;Nowadays trunk-based development as a branching model is preferred compared to something like Git Flow. But creating a CI/CD pipeline is more challenging since we deploy to every environment from the same branch. In this post, I create a CI/CD pipeline with GitHub actions that deploys to multiple environments. We will start with a basic implementation and improve it step by step.&lt;/p&gt;

&lt;p&gt;This post will not be about the basics of GitHub actions and won't go into details about trunk-based development. We start with a basic introduction to trunk-based development in order to have a shared understanding:&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief Introduction to Trunk-Based Development
&lt;/h2&gt;

&lt;p&gt;Trunk-based development involves frequent, small code check-ins to a shared code repository, typically known as &lt;strong&gt;the "trunk"&lt;/strong&gt;. This approach contrasts traditional development models (like &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"&gt;Git Flow&lt;/a&gt;), which often involve long, isolated development cycles followed by large code merges. In trunk-based development, teams are encouraged to commit their code to the trunk frequently, often multiple times per day. This allows for continuous integration and continuous deployment (CI/CD), as well as easier collaboration and code reviews. You also don't have branches per environment like a &lt;code&gt;development&lt;/code&gt; branch that is deploying to a development environment. You integrate all code on the "trunk".&lt;/p&gt;

&lt;p&gt;Trunk-Based Development is considered the best practice nowadays. You can learn more about it on &lt;a href="https://trunkbaseddevelopment.com/"&gt;trunkbaseddevelopment.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating GitHub Actions Workflows
&lt;/h2&gt;

&lt;p&gt;This is the basic CI/CD pipeline I'd like to create:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JmU_UwsI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cla1fr1j3d995ras5vmi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JmU_UwsI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cla1fr1j3d995ras5vmi.png" alt="Image description" width="265" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first, we will run some basic checks, then we build the artifact(s) and after that, we deploy it to a staging and a production environment. Yes, we can do a lot more like integration and E2E tests, more environment etc. but this should be sufficient to showcase the main points.&lt;/p&gt;

&lt;p&gt;I will be using an AWS CDK deployment as an example but the pipelines will be applicable for everything that creates some kind of a deployable artifact (like a &lt;code&gt;dist&lt;/code&gt; folder).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Basic GitHub Action Workflow
&lt;/h3&gt;

&lt;p&gt;Let us start with a very basic pipeline. We will build upon that and refine it in the following sections.&lt;/p&gt;

&lt;p&gt;This is how the code for a basic GitHub Action that deploys to a staging and a production environment could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yaml&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt; &lt;span class="c1"&gt;# your "trunk" branch&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-delopy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="c1"&gt;# Required for GH OIDC&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node and Cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16.18&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Type Check&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run type-check&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit Tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run synth&lt;/span&gt; &lt;span class="c1"&gt;# often 'npm run build'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1-node16&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEPLOY_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Artifact to Staging&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cdk deploy --app "./cdk.out/assembly-Staging" --all --concurrency 10 --method=direct --require-approval never&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Artifact to Production&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cdk deploy --app "./cdk.out/assembly-Prod" --all --concurrency 10 --method=direct --require-approval never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We trigger the pipeline on pushes to &lt;code&gt;main&lt;/code&gt; (our "trunk"). Remember, we don't have different branches per environment. We deploy to all our environments from this branch.&lt;/p&gt;

&lt;p&gt;We have one single job &lt;code&gt;build-and-delopy&lt;/code&gt; that includes all our steps of the pipeline. The steps are implementing the flow that is shown in the introduction of this chapter. We build the artifacts that should be deployed once and then deploy them to the different environments. We store our secrets in the GitHub secrets of the repository (&lt;code&gt;AWS_DEPLOY_ROLE_ARN&lt;/code&gt; in this case).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sidenote: I am using&lt;/em&gt; &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect"&gt;&lt;em&gt;Open ID Connect&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to get short-lived credentials for my AWS account. This is a security best practice and safer than using long-lived credentials like a username and password via environment variables.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While this already works, we can do better by leveraging &lt;a href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment"&gt;GitHub environments&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging GitHub Environments
&lt;/h3&gt;

&lt;p&gt;GitHub environments provide some additional features like specifying different secrets per environment (with the same name), having a deployment history per environment, and more. You can learn more about environments and their features in the &lt;a href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment"&gt;GitHub documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Environments have to be configured per job in our GitHub Action workflow. Currently, we only have a single job. Now we split it up into three distinct jobs: &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;deploy-staging&lt;/code&gt; and &lt;code&gt;deploy-production&lt;/code&gt;. This is what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yaml&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt; &lt;span class="c1"&gt;# your "trunk" branch&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node and Cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16.18&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Type Check&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run type-check&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit Tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run synth&lt;/span&gt; &lt;span class="c1"&gt;# often 'npm run build'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk-out&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out&lt;/span&gt; &lt;span class="c1"&gt;# often 'dist'&lt;/span&gt;
  &lt;span class="na"&gt;deploy-staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Staging&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
    &lt;span class="c1"&gt;# required to interact with GitHub's OIDC Token endpoint&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk-out&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1-node16&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEPLOY_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cdk deploy --app "./cdk.out/assembly-Staging" --all --concurrency 10 --method=direct --require-approval never&lt;/span&gt;
  &lt;span class="na"&gt;deploy-production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Production&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# build - if you want to deploy in parallel with Staging&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="c1"&gt;# required to interact with GitHub's OIDC Token endpoint&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk-out&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1-node16&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEPLOY_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cdk deploy --app "./cdk.out/assembly-Prod" --all --concurrency 10 --method=direct --require-approval never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new parts are the uploading and downloading of the artifact that is created in the &lt;code&gt;build&lt;/code&gt; job and the &lt;code&gt;environment&lt;/code&gt; per environment-specific job.&lt;/p&gt;

&lt;p&gt;By uploading and downloading the initially created artifacts we only have to actually build the artifacts once and can be sure that they are stable (and not differ due to different builds).&lt;/p&gt;

&lt;p&gt;After running this pipeline for the first to you'll the the "Environments" sections in the sidebar in your GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sgx3XSlh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl4onc3f2d10saxml58v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sgx3XSlh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl4onc3f2d10saxml58v.png" alt="A red arrow indicating the position where the environments are shown on the GitHub UI." width="880" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When clicking on it you will see the deployment history of that environment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fiU-gQ8D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wha87ajnvgoqntbpna24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fiU-gQ8D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wha87ajnvgoqntbpna24.png" alt="The deployment history for the production environment in the GitHub UI." width="880" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ideally, your history looks a lot greener&lt;/em&gt; 😅&lt;/p&gt;

&lt;p&gt;In addition to that, we can now re-run only the failed jobs (and not our whole pipeline). That was not possible with one job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8tdKZkVY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ql5vipfeng5lekbr4lrh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8tdKZkVY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ql5vipfeng5lekbr4lrh.png" alt="Three different jobs of the pipeline in the GitHub UI." width="880" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, this is an improvement, but there is one more thing: Often you don't want to automatically deploy to production but rather have a manual trigger in order to promote the artifact to production in order to have the opportunity to check the staging environment before actually releasing your changes to the user. How to do that with GitHub Actions?&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a Manual Trigger for a Production Deployment
&lt;/h3&gt;

&lt;p&gt;You can activate the "&lt;strong&gt;Required reviewers&lt;/strong&gt;" config for your production environment under Settings -&amp;gt; Environments -&amp;gt; production:&lt;/p&gt;

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

&lt;p&gt;That way you don't have to change anything in your pipeline. This is what it looks like:&lt;/p&gt;

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

&lt;p&gt;It will not be deployed to production without a review by an authorized person.&lt;/p&gt;

&lt;p&gt;Unfortunately, this simple solution is often not an option. For private repositories, it is only available to GitHub Enterprise customers and it has some other limitations like max. 6 authorized reviewers (teams are also possible). You can learn more about &lt;a href="https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments"&gt;reviewing deployments in the GitHub documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is an alternative to that using &lt;a href="https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases"&gt;releases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Releases for Manual Steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of one workflow with all of our jobs, we will now have two. One for building the artifacts, deploying to staging, and creating a release, and a second one for deploying to production if a specific release has been published.&lt;/p&gt;

&lt;p&gt;This is how the first workflow now looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy-staging.yaml&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt; &lt;span class="c1"&gt;# your "trunk" branch&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# &lt;/span&gt;
  &lt;span class="c1"&gt;# the 'build' and 'deploy-staging' jobs stay exactly the same; omitted&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="na"&gt;create-release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Release&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk-out&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out&lt;/span&gt;
      &lt;span class="c1"&gt;# we can't attack a folder to a release&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Zip Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;zip -r cdk.out.zip cdk.out&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Release Tag&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create-release-tag&lt;/span&gt;
        &lt;span class="c1"&gt;# left pads the run number with zeros to a length of 4; better alphabetical order&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "tag_name=r-$(printf %04d $GITHUB_RUN_NUMBER)" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Draft Release&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;softprops/action-gh-release@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;tag_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.create-release-tag.outputs.tag_name }}&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release ${{ steps.create-release-tag.outputs.tag_name }}&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;## Info&lt;/span&gt;
            &lt;span class="s"&gt;Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).&lt;/span&gt;

            &lt;span class="s"&gt;It was initialized by [${{ github.event.sender.login }}](${{ github.event.sender.html_url }}).&lt;/span&gt;

            &lt;span class="s"&gt;## How to Promote?&lt;/span&gt;
            &lt;span class="s"&gt;In order to promote this to prod, edit the draft and press **"Publish release"**.&lt;/span&gt;
          &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out.zip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We download the artifact from the &lt;code&gt;build&lt;/code&gt; job, zip it, and create a release as a draft attaching the zipped artifact. The body of the release can be written in markdown. You can add additional information to the release itself. Here you find a &lt;a href="https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push"&gt;reference to the information that is available in a push-triggered workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Releases are always tied to tags. I have created a basic tag name that uses the &lt;code&gt;GITHUB_RUN_NUMBER&lt;/code&gt; with a prefix. I also added some left padding to have the releases show up in alphabetical order. If you use semver or any other convention you can change the tag name there.&lt;/p&gt;

&lt;p&gt;This is an example release that has been created with the previously shown workflow:&lt;/p&gt;

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

&lt;p&gt;Editing that draft release and clicking on "Publish release" will trigger the next workflow that deploys the artifacts to production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy-production.yaml&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release-production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release to Production&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;startsWith(github.ref_name, 'r-')&lt;/span&gt; &lt;span class="c1"&gt;# the prefix we have added to the tag&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get Artifact from Release&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dsaltares/fetch-gh-release-asset@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.release.id }}&lt;/span&gt;
          &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdk.out.zip&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unzip Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unzip cdk.out.zip&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v1-node16&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_DEPLOY_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Artifact&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx cdk deploy --app "./cdk.out/assembly-Prod" --all --concurrency 10 --method=direct --require-approval never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow gets triggered on the &lt;code&gt;release&lt;/code&gt; &lt;code&gt;published&lt;/code&gt; event which gets triggered after a release, pre-release, or draft of a release was published. You can look up the &lt;a href="https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=published#release"&gt;details on the release published event in the GitHub documentation&lt;/a&gt;. In addition to that, we only run the &lt;code&gt;release-production&lt;/code&gt; job if the reference of the release that triggered the workflow starts with the prefix for the tag we have created earlier.&lt;/p&gt;

&lt;p&gt;That is it. You can see the published releases in the sidebar on the GitHub UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tto5Bsuu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1670914248931/ln0OxILWB.png%2520align%3D%2522center%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tto5Bsuu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1670914248931/ln0OxILWB.png%2520align%3D%2522center%2522" alt="" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I like this approach since you have GitHub releases for your actual production releases. That releases have the deployed artifacts attached. That way you can re-deploy any previously deployed artifact easily. This &lt;strong&gt;enables rollbacks to previous releases&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;GitHub Actions provide us with a lot of opportunities. I myself haven't used features like up-/downloading artifacts, environments, creating releases etc. Knowing about them opens up new doors. I now prefer having multiple jobs that I can re-run independently instead of a single job that is doing everything. &lt;em&gt;(I was actually wondering for a long time what the difference between "Re-run all jobs" vs. "Re-run failed jobs" is. There were none for me.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hopefully, you learned a thing and maybe can use it in your own GitHub Action workflows.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>cicd</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My thoughts after trying to port a Shopify store from NextJS to Shopify Hydrogen</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Sun, 30 Oct 2022 10:43:42 +0000</pubDate>
      <link>https://dev.to/jannikwempe/my-thoughts-after-trying-to-port-a-shopify-store-from-nextjs-to-shopify-hydrogen-ai4</link>
      <guid>https://dev.to/jannikwempe/my-thoughts-after-trying-to-port-a-shopify-store-from-nextjs-to-shopify-hydrogen-ai4</guid>
      <description>&lt;p&gt;A while ago I created a Shopify shop for my little brother who is selling handmade, unique furniture: &lt;a href="https://www.xn--glckweiser-beb.de/"&gt;glückweiser.de&lt;/a&gt; (all German; currently mainly &lt;a href="https://www.etsy.com/de/shop/Glueckweiser"&gt;selling on Etsy&lt;/a&gt;). It is written in NextJS 12 and uses Shopify's GraphQL Storefront API. The Developer Experience (DX) is okay. Everything is nice and typesafe with Typescript, graphql-codegen and react-query. BUT it is quite some manual work. Especially dealing with the cart. Initializing it, storing the id, updating etc.&lt;/p&gt;

&lt;p&gt;I like trying out new things and this is why I thought I'll give &lt;a href="https://hydrogen.shopify.dev/"&gt;Shopify Hydrogen&lt;/a&gt; a shot and recreate the shop using it. In this article, I want to share my experiences and thoughts after a few days of coding in Hydrogen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Shopify Hydrogen?
&lt;/h2&gt;

&lt;p&gt;Before I go ahead and share my experiences, I want to give a quick intro to Hydrogen:&lt;/p&gt;

&lt;p&gt;These are the main features as per the Hydrogen website:&lt;/p&gt;

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

&lt;p&gt;I would say its main selling point is obviously the integration with Shopify. It comes with hooks, components and types to make it really easy to interact with the Shopify API. (They also use an &lt;a href="https://github.com/Shopify/hydrogen/blob/6310974ff8f8fdcf742bc7a1f5af1370e221c6fa/packages/hydrogen/src/components/CartProvider/useCartAPIStateMachine.client.tsx"&gt;XState state machine for the cart&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;From an architectural perspective, it heavily relies on &lt;a href="https://shopify.dev/custom-storefronts/hydrogen/framework/react-server-components"&gt;React server components&lt;/a&gt; and &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;. React server components allow for fetching data right in the React component. It is run on the server and streamed to the client. You can safely use secrets there since only the resulting HTML gets streamed to the client. (&lt;a href="https://nextjs.org/docs/advanced-features/react-18/server-components"&gt;React server components were just released in NextJS 13 a few days ago.&lt;/a&gt;). React server components require quite some mind shift because previously components were primarily run on the client (neither a pro nor a con of Hydrogen for me since it's related to React).&lt;/p&gt;

&lt;p&gt;In addition to that, Hydrogen comes with out-of-the-box support for Tailwind and Typescript.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about Hydrogen
&lt;/h2&gt;

&lt;p&gt;Let us start with the pros of using Hydrogen first:&lt;/p&gt;

&lt;h3&gt;
  
  
  Shopify integration
&lt;/h3&gt;

&lt;p&gt;As mentioned previously, this is the main selling point in my eyes. You configure your Shopify integration in &lt;code&gt;hydrogen.config.ts&lt;/code&gt; and can start using hooks like &lt;code&gt;useShopQuery&lt;/code&gt; that let you fetch data from the Storefront API. The types are also shipped with Hydrogen.&lt;/p&gt;

&lt;p&gt;Besides that, it comes with &lt;a href="https://shopify.dev/api/hydrogen/components"&gt;handy components&lt;/a&gt; that are required for almost any shop: &lt;code&gt;Money&lt;/code&gt;, &lt;code&gt;AddToCartButton&lt;/code&gt;, &lt;code&gt;Image&lt;/code&gt; and many more.&lt;/p&gt;

&lt;p&gt;This nice integration lets you get started pretty quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components provide primitives
&lt;/h3&gt;

&lt;p&gt;Hydrogen components are primitives. Hydrogen doesn't provide components like &lt;code&gt;Cart&lt;/code&gt; or &lt;code&gt;ProductPage&lt;/code&gt;. They are different for most shops anyway. Instead, Hydrogen provides small building blocks. These can be styled and customized easily with Tailwind (and most likely CSS). They are focused on functionality rather than design (which makes sense).&lt;/p&gt;

&lt;h3&gt;
  
  
  Up-to-date tech stack
&lt;/h3&gt;

&lt;p&gt;I think Hydrogen was the first framework heavily betting on React server components. Now that NextJS 13 is also defaulting to server components for routes (in the &lt;code&gt;/app&lt;/code&gt; folder), it is probably safe to say that server components are the future of React. Mastering Hydrogen will improve your overall React skills and will be transferable to other frameworks that are also relying on React server components.&lt;/p&gt;

&lt;p&gt;In addition to that, Vite is also probably one of the most modern dev- and build tools out there. (At least until &lt;a href="https://turbo.build/pack"&gt;Turbopack&lt;/a&gt; will eventually takes over 🤪)&lt;/p&gt;

&lt;h2&gt;
  
  
  What I don't like about Hydrogen
&lt;/h2&gt;

&lt;p&gt;Besides the pros, there are also some cons:&lt;/p&gt;

&lt;h3&gt;
  
  
  Development and production environments behave differently
&lt;/h3&gt;

&lt;p&gt;It is always painful if a development environment is very different from the production environment. This opens the doors for bugs in production that you were not able to catch during development.&lt;/p&gt;

&lt;p&gt;Unfortunately, this is exactly my experience: In production, I started seeing errors that my client components could not be found. That crashed the whole app. I was able to fix this be adding &lt;code&gt;{ optimizeBoundaries: false }&lt;/code&gt; to the &lt;code&gt;hydrogen&lt;/code&gt; Vite plugin – but I have honestly no idea what this is doing and what other implications it might have.&lt;/p&gt;

&lt;p&gt;Another issue: I created an API route for sending emails. I was using &lt;code&gt;nodemailer&lt;/code&gt; for that. Everything works fine in dev, but I saw various errors when trying to build the app. It took me a while to find out that Hydrogen is creating edge functions for API routes. Edge functions only provide a limited feature set and therefore some node internals were not working.&lt;/p&gt;

&lt;p&gt;I made it work by ditching &lt;code&gt;nodemailer&lt;/code&gt; and using the Mailgun API with fetch directly. It worked on dev, and the bundling also succeeded but it failed after deployment (on Vercel) because the environment variables could not be found anymore (yes, I have added them to Vercel).&lt;/p&gt;

&lt;p&gt;The DX is lacking and I was encountering multiple such issues... A huge bummer for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docs leave room for improvement
&lt;/h3&gt;

&lt;p&gt;You can imagine that I was reading the docs and API references a lot. The API routes are explained in a few sentences. Potential limitations or something were not covered, edge functions or worker environments were not mentioned. I had to figure out a lot of stuff myself...&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional thoughts
&lt;/h2&gt;

&lt;p&gt;I think Hydrogen has a high potential and I would probably default to it for creating custom Shopify shops. But the DX is not there yet. I encountered too many roadblocks that took me a while to resolve.&lt;/p&gt;

&lt;p&gt;I would prefer to have the primitives and nice Shopify integration without having to adapt a whole new framework. I think this is exactly what &lt;a href="https://shopify.dev/custom-storefronts/hydrogen/alternate-frameworks"&gt;Hydrogen UI&lt;/a&gt; is about (not in beta yet and I am not sure which features are Hydrogen and which are Hydrogen UI). I would really like to have Hydrogen features be easily usable in NextJS. I wonder if it would have been a better idea for Shopify to focus on something like Hydrogen UI rather than creating a full framework. All the Vite SSR stuff is the most complicated of Hydrogen for sure. Maybe that has something to do with &lt;a href="https://shopify.dev/custom-storefronts/oxygen/getting-started"&gt;Shopify's Oxygen&lt;/a&gt; – the recommended deployment target for Hydrogen? I don't know...&lt;/p&gt;

&lt;p&gt;Most likely Hydrogen will improve a lot in the future but I currently have a bad gut feeling about fully committing to it after my initial experiences...&lt;/p&gt;

</description>
      <category>react</category>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Securely Use Secrets in AWS Lambda?</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Mon, 10 Oct 2022 04:47:40 +0000</pubDate>
      <link>https://dev.to/jannikwempe/how-to-securely-use-secrets-in-aws-lambda-3hg1</link>
      <guid>https://dev.to/jannikwempe/how-to-securely-use-secrets-in-aws-lambda-3hg1</guid>
      <description>&lt;p&gt;It is quite common to need a secret value of some kind in a Lambda function. Either for a database connection, a 3-rd party service, or whatever else. But how to securely use secrets in your Lambda? &lt;/p&gt;

&lt;p&gt;In this post, I am going to tell you why &lt;strong&gt;environment variables are not the tool for the job&lt;/strong&gt; and what the preferred way looks like. &lt;/p&gt;

&lt;p&gt;I will also provide you with an example implementation showing you how to do better using AWS CDK and TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Environment Variables Are Not the Best Solution for Accessing Secrets
&lt;/h2&gt;

&lt;p&gt;I am sure I don't have to tell you why it is a bad idea to put secrets in plain text into your code base. But why aren't environment variables very secure either? AWS suggests to better not using environment variables for your secrets:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To increase database security, we recommend that you use AWS Secrets Manager &lt;strong&gt;instead of environment variables&lt;/strong&gt; to store database credentials.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(from &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-encryption"&gt;Securing environment variables&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This tweet from &lt;a href="https://twitter.com/theburningmonk"&gt;Yan Cui (@theburningmonk)&lt;/a&gt; sums up the reasons pretty well:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--G_lMwsJ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1313922196339920898/dc1SALIM_normal.jpg" alt="Yan Cui profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Yan Cui
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/theburningmonk"&gt;@theburningmonk&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Depends on if you put them as plaintext in env vars - if a function is compromised then env vars is the 1st place attackers would look. There have been multiple instances of compromised NPM packages that steal env vars when initialized. This affects lambda too.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      09:35 AM - 02 Jun 2021
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1400023348998987783" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1400023348998987783" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1400023348998987783" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;There are malicious packages out there sending your environment variables somewhere else. Just google something like &lt;a href="https://www.google.com/search?q=npm+package+malicious+environment+variables&amp;amp;oq=npm+package+malicious+environment+variables"&gt;"npm package malicious environment variables"&lt;/a&gt; and you will find lists of malicious packages. Often they create packages with almost identical names than widely used packages. If you do a typo while installing a package you could end up sending your secrets to a bad actor. &lt;strong&gt;This is not an issue that is specific to AWS Lambda&lt;/strong&gt; – but the solution I am going to show you is.&lt;/p&gt;

&lt;p&gt;In addition to that, secrets stored in environment variables will be visible to everyone having access to that Lambda in the AWS console. With other services (I will get to them in the next section) you can restrict who is able to see the secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Securely Access Secrets in AWS Lambda?
&lt;/h2&gt;

&lt;p&gt;Now that we know why using environment variables for secrets is a bad idea, how can we do better?&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving Secrets at Runtime
&lt;/h3&gt;

&lt;p&gt;One solution has already been mentioned in the quote in the previous chapter: &lt;a href="https://aws.amazon.com/secrets-manager/"&gt;AWS Secrets Manager&lt;/a&gt;. Another popular solution is &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html"&gt;AWS Systems Manager Parameter Store&lt;/a&gt;. Both of them allow you to securely store secrets and retrieve them using the &lt;code&gt;aws-sdk&lt;/code&gt;. They provide a different set of features but this is out of scope for this article – both of them are doing the job for the use case I will be showing you. There are other solutions out there as well, but AWS Secrets Manager and AWS Systems Manager Parameter Store (SSM Parameter Store) are the most common ones. I will go ahead and use SSM Parameter Store for further examples.&lt;/p&gt;

&lt;p&gt;You have different options on how to retrieve and use secrets from SSM – as always, all of them have different trade-offs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieve secrets &lt;strong&gt;inside of the Lambda handler&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Retrieve secrets &lt;strong&gt;during Lambda &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-ib"&gt;init phase&lt;/a&gt;&lt;/strong&gt; (outside of handler) &amp;amp; &lt;strong&gt;cache them forever&lt;/strong&gt; (= for the time of the Lambda execution context)&lt;/li&gt;
&lt;li&gt;Retrieve secrets &lt;strong&gt;during Lambda init phase&lt;/strong&gt; &amp;amp; &lt;strong&gt;cache them for a certain period&lt;/strong&gt;, e.g. 5min&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trade-off you have to make is between performance &amp;amp; price (less API calls are cheaper and faster) and flexibility (you can change secrets immediately if you don't cache them).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AxBXLVwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbmbtz0tulhxw9agglky.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AxBXLVwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbmbtz0tulhxw9agglky.jpg" alt="trade-offs retrieving secrets in AWS Lambda" width="571" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving Secrets in AWS Lambda using AWS CDK
&lt;/h3&gt;

&lt;p&gt;Now that we know the theory, let's go ahead and implement it. I will be using AWS CDK and TypeScript for this.&lt;/p&gt;

&lt;p&gt;Before we go ahead with writing actual code, let's create a secret we want to retrieve. We can create it via the AWS console or via the &lt;code&gt;awscli&lt;/code&gt; like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws ssm put-parameter --name "/dev/service-a/secret-key" --type "SecureString" --value "my-super-secret-secret"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: I like to use a naming convention like &lt;code&gt;/[stage]/[service]/[parameter]&lt;/code&gt; in order to keep everything nice and organized and to be able to easily construct and access parameters for different environments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can find the code in my &lt;a href="https://github.com/JannikWempe/lambda-retrieve-secrets"&gt;JannikWempe/lambda-retrieve-secrets&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;This is how you get access to that previously created secret in CDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LambdaRetrieveSecretsStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceASecretKeyParameter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;StringParameter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromSecureStringParameterAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ServiceASecretKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;parameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dev/service-a/secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// replace with your parameter name&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can reference that secret and use CDKs utilities in order to grant access to that secret. Let us add a Lambda function, pass the &lt;code&gt;parameterName&lt;/code&gt; to it as an environment variable, and grant access to read that secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LambdaRetrieveSecretsStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceASecretKeyParameter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;StringParameter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromSecureStringParameterAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ServiceASecretKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;parameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dev/service-a/secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// replace with your parameter name&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretsExampleLambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SecretsExampleLambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_16_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;SERVICE_A_SECRET_KEY_PARAMETER_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;serviceASecretKeyParameter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parameterName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;serviceASecretKeyParameter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grantRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretsExampleLambda&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it from the CDK side. Now let us create the handler and retrieve that secret. I like to use &lt;a href="https://middy.js.org/"&gt;middy&lt;/a&gt; which describes itself as &lt;em&gt;"stylish Node.js middleware engine for AWS Lambda"&lt;/em&gt;. It offers some helpful middlewares like &lt;a href="https://middy.js.org/docs/middlewares/ssm"&gt;ssm&lt;/a&gt; which will help us retrieve and cache values from SSM Parameter Store. (Middy provides &lt;a href="https://middy.js.org/docs/middlewares/intro"&gt;various other official middlewares&lt;/a&gt; including one for Secrets Manager.) I prefer a middleware for this because it keeps the code for retrieving the secret out of your handler which should deal with actual business logic.&lt;/p&gt;

&lt;p&gt;The handler could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;middy&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ssm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy/ssm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_A_SECRET_KEY_PARAMETER_NAME&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_A_SECRET_KEY_PARAMETER_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVICE_A_SECRET_KEY_PARAMETER_NAME environment variable is not set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ⚠️⚠️⚠️ you should never log secrets; just for demo purposes ⚠️⚠️⚠️&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_A_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;SERVICE_A_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_A_SECRET_KEY_PARAMETER_NAME&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_A_SECRET_KEY_PARAMETER_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cacheExpiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 5min&lt;/span&gt;
  &lt;span class="na"&gt;setToContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are wrapping the handler with &lt;code&gt;middy&lt;/code&gt; in order to be able to &lt;code&gt;use&lt;/code&gt; the &lt;code&gt;ssm&lt;/code&gt; middleware which will retrieve the decrypted secret for us.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetchData&lt;/code&gt; lets us define the name we want to use for the retrieved parameter as a key and the &lt;code&gt;parameterName&lt;/code&gt; as the value.&lt;/p&gt;

&lt;p&gt;Make sure to set &lt;code&gt;setToContext: true&lt;/code&gt;. That way the secret is accessible via the Lambda context. It will not end up in the environment variables.&lt;/p&gt;

&lt;p&gt;Also, I am setting a &lt;code&gt;cacheExpiry&lt;/code&gt; of 5min here. That way the Lambda will retrieve the secret in the init phase and will reuse that value for 5min in subsequent requests in that execution environment. If the execution environment is still running after 5min, the secret will again be fetched during the next invocation. This is somewhere in the middle of the trade-off spectrum between cost &amp;amp; performance and flexibility.&lt;/p&gt;

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

&lt;p&gt;There are safer ways to use secrets in your AWS Lambda than by using environment variables. It is not much harder to use AWS services like Secrets Manager or SSM to retrieve secrets at runtime. In fact, it will become easier to manage the secrets from a central place. You can easily rotate secrets in a central place and configure permissions only showing the secrets to authorized people. &lt;strong&gt;From now on, you should not use environment variables for secrets.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscdk</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Mastering AWS CDK Aspects</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Thu, 29 Sep 2022 06:33:25 +0000</pubDate>
      <link>https://dev.to/jannikwempe/mastering-aws-cdk-aspects-40g8</link>
      <guid>https://dev.to/jannikwempe/mastering-aws-cdk-aspects-40g8</guid>
      <description>&lt;h2&gt;
  
  
  CDK Aspects Introduction
&lt;/h2&gt;

&lt;p&gt;CDK Aspects are a powerful tool provided by the AWS Cloud Development Kit (CDK). They are utilizing the &lt;a href="https://refactoring.guru/design-patterns/visitor"&gt;Visitor Pattern&lt;/a&gt;. By applying a CDK Aspect to a specific &lt;code&gt;scope&lt;/code&gt;, you get access to every child node within it. You can inspect them or alter them. That &lt;code&gt;scope&lt;/code&gt; can be any &lt;code&gt;IConstruct&lt;/code&gt; which includes &lt;code&gt;App&lt;/code&gt; and &lt;code&gt;Stack&lt;/code&gt; as they extend &lt;code&gt;Construct&lt;/code&gt;. Therefore an Aspect can be applied at various levels.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CDK Aspects are a way to apply an operation to every construct in a given scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This should be enough on a high level about what CDK Aspects are for this post. I have written a post on the &lt;a href="https://aws.hashnode.com/"&gt;Learn AWS hashnode blog&lt;/a&gt; about &lt;a href="https://aws.hashnode.com/the-power-of-aws-cdk-aspects"&gt;The Power of AWS CDK Aspects&lt;/a&gt;. Read that post if you are interested in some more theory.&lt;/p&gt;

&lt;p&gt;In this post, I will show you a few possible implementations of custom CDK Aspects on how you can leverage third-party Aspects. We will create Aspects that alter your infrastructure, that show errors, that take arguments and that don't take arguments.&lt;/p&gt;

&lt;p&gt;The goal is that you should be able to write your own Aspects and have a better idea of possible use cases for CDK Aspects.&lt;/p&gt;

&lt;p&gt;You can find all the code in a CDK app that you can play around with in my GitHub repository &lt;a href="https://github.com/JannikWempe/cdk-aspects-examples"&gt;JannikWempe/cdk-aspects-examples&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Custom CDK Aspects
&lt;/h2&gt;

&lt;p&gt;Aspects have a small interface. This is what we have to implement creating our own custom Aspects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IAspect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IConstruct&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This terms &lt;code&gt;visit&lt;/code&gt; (based on Visitor Pattern), &lt;code&gt;node&lt;/code&gt;, and &lt;code&gt;IConstruct&lt;/code&gt; should already be familiar to you after the introduction. The &lt;code&gt;visit&lt;/code&gt; method will be called for every &lt;code&gt;node&lt;/code&gt; within the &lt;code&gt;scope&lt;/code&gt; you are applying the Aspect to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Applying Tags
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/tagging.html"&gt;standard way of applying tags in a CDK&lt;/a&gt; app is by using &lt;code&gt;Tags.of(scope).add(key, value)&lt;/code&gt;. You know what? &lt;a href="https://github.com/aws/aws-cdk/blob/f834a4537643b32131076111be0693c6f8f96b24/packages/@aws-cdk/core/lib/tag-aspect.ts#L152-L154"&gt;That is already using an Aspect&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But maybe you want something more custom and make certain tags required.&lt;/p&gt;

&lt;p&gt;This is how a custom &lt;code&gt;ApplyTags&lt;/code&gt; Aspect could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt; 

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ApplyTags&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IAspect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IConstruct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TagManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTaggable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applyTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;applyTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ITaggable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;visit&lt;/code&gt; we narrow down the &lt;code&gt;node&lt;/code&gt; to a construct that is taggable by checking if &lt;code&gt;TagManager.isTaggable(node)&lt;/code&gt; is truthy. After that, we go ahead and add the desired tags.&lt;/p&gt;

&lt;p&gt;TypeScript helps here because you won't be able to just call &lt;code&gt;setTag&lt;/code&gt; on any &lt;code&gt;IConstruct&lt;/code&gt;. You have to filter out the resources that are not taggable. This is a common pattern for Aspects: You narrow down all the nodes to the resources you want to act upon.&lt;/p&gt;

&lt;p&gt;This is how you apply &lt;code&gt;ApplyTags&lt;/code&gt; on the &lt;code&gt;App&lt;/code&gt;-level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myStack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MyStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appAspects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Aspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;appAspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ApplyTags&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CDK Aspects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jannik Wempe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You first call &lt;code&gt;Aspects.of(scope)&lt;/code&gt; to get access to the &lt;code&gt;Aspects&lt;/code&gt; within &lt;code&gt;scope&lt;/code&gt; and add your own Aspect to it.&lt;/p&gt;

&lt;p&gt;This will add three tags to all taggable resources that are within your &lt;code&gt;app&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Enabling S3 Bucket Versioning
&lt;/h3&gt;

&lt;p&gt;This example will show you how to alter constructs within the scope. Use this power responsively as you will end up in a mess if you overuse this.&lt;/p&gt;

&lt;p&gt;You want to enable bucket versioning for all of your buckets? You could create your own &lt;code&gt;VersionedBucket&lt;/code&gt; by extending &lt;code&gt;Bucket&lt;/code&gt;. But that is probably not the best idea. You would end up maintaining your own L2 construct and the requirements for that bucket will increase. You will end up with a custom bucket construct that would have tons of props in order to be suitable for all sorts of scenarios. Using an Aspect for that is more maintainable – and it requires fewer lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;EnableBucketVersioning&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IAspect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IConstruct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;CfnBucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versioningConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. All of your &lt;code&gt;CfnBucket&lt;/code&gt;s within &lt;code&gt;scope&lt;/code&gt; will be versioned.&lt;/p&gt;

&lt;p&gt;Note that all &lt;code&gt;Bucket&lt;/code&gt;s (L2 construct) are a &lt;code&gt;CfnBucket&lt;/code&gt; (L1 construct) but &lt;code&gt;CfnBucket&lt;/code&gt;s are not necessarily a &lt;code&gt;Bucket&lt;/code&gt;. Double check that you are narrowing down to the desired ones by adding some &lt;code&gt;console.log&lt;/code&gt;s – as a real developer does 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Enforcing Minimum Lambda Node Runtime Version
&lt;/h3&gt;

&lt;p&gt;As mentioned in the previous example, altering all kinds of constructs through Aspects can end up in a mess that is hard to debug. Displaying an error and letting the developer intentionally change the code might be the better option. This is what we will do in this example.&lt;/p&gt;

&lt;p&gt;Let us create an Aspect that checks all Lambda functions runtime versions and enforces a minimum NodeJS version being used. We pass a &lt;code&gt;minimumNodeRuntimeVersion: Runtime&lt;/code&gt; into the &lt;code&gt;constructor&lt;/code&gt; of our &lt;code&gt;EnforceMinimumLambdaNodeRuntimeVersion&lt;/code&gt; Aspect &lt;em&gt;(Yes, I know, it is a creative name 😅)&lt;/em&gt;. The Aspect would check every Lambda functions runtime and adds an error to the functions &lt;code&gt;Annotations&lt;/code&gt; if the check fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;EnforceMinimumLambdaNodeRuntimeVersion&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IAspect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;RuntimeFamily&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Minimum NodeJS runtime version must be a NodeJS runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IConstruct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;CfnFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="c1"&gt;// runtime is optional for functions not being deployed from a package&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Runtime not specified for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actualNodeJsRuntimeVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minimumNodeJsRuntimeVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actualNodeJsRuntimeVersion&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;minimumNodeJsRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Annotations&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Node.js runtime version &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is less than the minimum version &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;minimumNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;parseNodeRuntimeVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runtimeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtimeVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runtimeName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;runtimeVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is slightly more complex but it follows the same principles as the previous example. The main difference is the &lt;code&gt;Annotations.of(node).addError()&lt;/code&gt; part. We don't throw an error that would abort the synthesis and show an ugly, unhelpful stack tract, but we are adding an error annotation to the construct itself. This is what error annotations would 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;[Error at /MyStack/MyLambda1/Resource] Node.js runtime version nodejs12.x is less than the minimum version nodejs16.x.
[Error at /MyStack/MyLambda2/Resource] Node.js runtime version nodejs12.x is less than the minimum version nodejs16.x.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that it shows two errors. The synthesis doesn't stop after the first one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 4: Configure Lambda Log Groups
&lt;/h3&gt;

&lt;p&gt;This one is also different from the previous ones. We now want to add an actual resource. Lambda functions are creating their CloudWatch log groups implicitly. You won't be able to see the log groups in the synthesized CloudFormation template but they will be created for you. That is why we can't just narrow the &lt;code&gt;node&lt;/code&gt; in &lt;code&gt;visit&lt;/code&gt; down to &lt;code&gt;CfnLogGroup&lt;/code&gt;. The log group won't be there.&lt;/p&gt;

&lt;p&gt;In order to customize a log group of a Lambda we have to explicitly create it with the &lt;code&gt;logGroupName&lt;/code&gt; being &lt;code&gt;/aws/lambda/[REPLACE_WITH_LAMBDA_FN_NAME]&lt;/code&gt;. We can pass configurations to that explicitly created log group.&lt;/p&gt;

&lt;p&gt;This is what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LambdaLogGroupConfig&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IAspect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;logGroupProps&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LogGroupProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logGroupName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logGroupProps&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LogGroupProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logGroupName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;logGroupProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logGroupProps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;construct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IConstruct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;construct&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;CfnFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createLambdaLogGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;construct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;createLambdaLogGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CfnFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LogGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LogGroup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;logGroupProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;logGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/aws/lambda/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be honest, this one was hard for me as it has some caveats. Thanks, Glib Shpychka for helping me out on the &lt;a href="https://cdk.dev/"&gt;cdk.dev Slack channel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can pass all &lt;code&gt;LogGroupProps&lt;/code&gt; to the Aspect excluding the &lt;code&gt;logGroupName&lt;/code&gt; as this is the prop that connects the &lt;code&gt;LogGroup&lt;/code&gt; to the &lt;code&gt;CfnFunction&lt;/code&gt;. We again narrow down the node to the construct we are interested in: &lt;code&gt;CfnFunction&lt;/code&gt;. Now we create a &lt;code&gt;LogGroup&lt;/code&gt; and are using the &lt;code&gt;CfnFunction&lt;/code&gt; construct itself as the &lt;code&gt;scope&lt;/code&gt; for that &lt;code&gt;LogGroup&lt;/code&gt;. This helps us to avoid conflicts in the CDK-generated logical IDs as the scope will be used to generate a prefix.&lt;/p&gt;

&lt;p&gt;Now you might wonder what the &lt;code&gt;lambda.ref&lt;/code&gt; is about and why it's not just &lt;code&gt;lambda.functionName&lt;/code&gt;. This is the tricky part.&lt;/p&gt;

&lt;p&gt;It is &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/best-practices.html#best-practices-constructs#best-practices-apps-names"&gt;best practice to not assign specific resource names to your constructs but rather let CDK generate them for you&lt;/a&gt;. If you log &lt;code&gt;lambda.functionName&lt;/code&gt; to the console you would see something like this: &lt;code&gt;${Token[TOKEN.246]}&lt;/code&gt;. It is a &lt;code&gt;Token&lt;/code&gt;. What is a &lt;code&gt;Token&lt;/code&gt;?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tokens represent values that can only be resolved at a later time in the lifecycle of an app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(from &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/tokens.html"&gt;CDK docs - Tokens&lt;/a&gt;&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;I tried different kinds of things to resolve the &lt;code&gt;Token&lt;/code&gt; (e.g. checking &lt;code&gt;Token.isUnresolved&lt;/code&gt;) but nothing was working. The solution was to use &lt;code&gt;CfnRefElement.ref&lt;/code&gt; which lets you access the CloudFormation &lt;code&gt;{ Ref }&lt;/code&gt; element – the physical name. &lt;/p&gt;

&lt;p&gt;With  &lt;code&gt;LambdaLogGroupConfig&lt;/code&gt; you could configure all Lambda functions log groups based on the environment like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;appAspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaLogGroupConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;retention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_MONTH&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_WEEK&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// OR&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myProdStackAspects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Aspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myProdStack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;myProdStackAspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaLogGroupConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;retention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_MONTH&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myDevStackAspects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Aspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myDevStack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;myDevStackAspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaLogGroupConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;retention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_WEEK&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using 3rd-Party CDK Aspects
&lt;/h2&gt;

&lt;p&gt;There are already useful CDK Aspects out there that are ready to be used in your CDK application. In this section I want to give you an example for one of them: One of them is &lt;a href="https://github.com/cdklabs/cdk-nag"&gt;cdk-nag&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk-nag&lt;/code&gt; contains several Aspects to check your applications for best practices. It is especially useful if you need to be HIPAA-compliant or have other compliance requirements. It is inspired by &lt;a href="https://github.com/stelligent/cfn_nag"&gt;cfn_nag&lt;/a&gt; which is a a tool checking for patterns in your CloudFormation templates.&lt;/p&gt;

&lt;p&gt;After installing &lt;code&gt;cdk-nag&lt;/code&gt; with your favorite package manager (e.g. &lt;code&gt;npm install cdk-nag&lt;/code&gt;) you can use one of the Aspects just like the others above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MyStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appAspects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Aspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;appAspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AwsSolutionsChecks&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AwsSolutionChecks&lt;/code&gt; &lt;a href="https://github.com/cdklabs/cdk-nag/blob/main/RULES.md#awssolutions"&gt;includes a lot of rules&lt;/a&gt; which is why you initial output after trying to synthesize your app could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Error at /MyStack/CdkAssertionsQueue/Resource] AwsSolutions-SQS2: The SQS Queue does not have server-side encryption enabled.
[Error at /MyStack/CdkAssertionsQueue/Resource] AwsSolutions-SQS3: The SQS queue does not have a dead-letter queue (DLQ) enabled or have a cdk-nag rule suppression indicating it is a DLQ.
[Error at /MyStack/CdkAssertionsQueue/Resource] AwsSolutions-SQS4: The SQS queue does not require requests to use SSL.
[Error at /MyStack/MyBucket/Resource] AwsSolutions-S1: The S3 Bucket has server access logs disabled.
[Error at /MyStack/MyBucket/Resource] AwsSolutions-S2: The S3 Bucket does not have public access restricted and blocked.
[Error at /MyStack/MyBucket/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each rule has an identifier like &lt;code&gt;AwsSolutions-S2&lt;/code&gt;. You can &lt;a href="https://github.com/cdklabs/cdk-nag/blob/main/RULES.md#awssolutions"&gt;turn off rules individually&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;We had a look at different possibilities you have when creating your own CDK Aspects and have used a 3rd-party Aspect as well. By now you should have a good idea of how to work with CDK Aspects. Maybe you even have some use cases in mind in which Aspects could be helpful.&lt;/p&gt;

&lt;p&gt;There is also one other useful use case that I haven't explicitly shown above: You can alter constructs that you don't have direct access to. Imagine using a 3rd party L3 construct that does not expose ways to customize one of the underlying resources. You can create a CDK Aspect and apply it to that 3rd party construct.&lt;/p&gt;

&lt;p&gt;All the code is on GitHub &lt;a href="https://github.com/JannikWempe/cdk-aspects-examples"&gt;JannikWempe/cdk-aspects-examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading this article ✌🏼&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscdk</category>
    </item>
    <item>
      <title>AWS Beginner: AWS is different. What are AWS Accounts, IAM Users and Root User?</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Wed, 17 Aug 2022 20:00:54 +0000</pubDate>
      <link>https://dev.to/jannikwempe/aws-beginner-aws-is-different-what-are-aws-accounts-iam-users-and-root-user-1i95</link>
      <guid>https://dev.to/jannikwempe/aws-beginner-aws-is-different-what-are-aws-accounts-iam-users-and-root-user-1i95</guid>
      <description>&lt;p&gt;Things are confusing when you are just starting to use AWS. You don't just create an account and get started as you do with other services. You come across terms like "Account", "User" and "Root User" and maybe ask yourself how they differ. This post should help understand these terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Sign Up for an AWS Account
&lt;/h2&gt;

&lt;p&gt;If you &lt;a href="https://portal.aws.amazon.com/billing/signup"&gt;sign up for an AWS account&lt;/a&gt; you will be asked to enter a "Root user email address" and an "AWS account name":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PsvsBh00--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1660759462081/pqv6ArCLL.png%2520align%3D%2522center%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PsvsBh00--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1660759462081/pqv6ArCLL.png%2520align%3D%2522center%2522" alt="AWS sign up screen" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you finish the signup process, you will end up with an account (identified by a 12-digit account ID and an account name, also known as account alias) and some credentials (email and password of your root user) to enter that account.&lt;/p&gt;

&lt;p&gt;This is what the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html"&gt;AWS docs say about the root user&lt;/a&gt; in a red warning callout:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We strongly recommend that you do &lt;strong&gt;not use the root user for your everyday tasks&lt;/strong&gt;, even the administrative ones. Instead, adhere to the best practice of using the root user only to &lt;strong&gt;create your first IAM user&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, so you should create an IAM user (often just referred to as user; this is &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html"&gt;how you can create an IAM user&lt;/a&gt;) but what is the difference between an IAM user and the root user? And usually, if you sign up for a service you create an account with credentials and end up with one thing: an account. Why do you have (at least) three account-related things in AWS? How are they different? Before we will get to the definitions we will have a look at how to sign in using either the root user or an IAM user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign In to your account
&lt;/h2&gt;

&lt;p&gt;This is how the sign-in screen looks like when you click on the "Sign In" button on &lt;a href="https://aws.amazon.com/console/"&gt;https://aws.amazon.com/console/&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oqcvmNIk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1660763912747/hqdSRaXss.png%2520align%3D%2522center%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oqcvmNIk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1660763912747/hqdSRaXss.png%2520align%3D%2522center%2522" alt="AWS sign in screen" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can either go ahead and sign in as a root user by just providing the root user email address and the password or you can sign in as an IAM user by providing the account ID or account alias first and the IAM user name and password in the second step:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yyGl7qfm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1660763779171/uhl7ljSG5.png%2520align%3D%2522center%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yyGl7qfm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1660763779171/uhl7ljSG5.png%2520align%3D%2522center%2522" alt="AWS sign in screen IAM user" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note how you don't have to provide the account ID for the root user as that user has to be unique globally.&lt;/p&gt;

&lt;p&gt;Also, you can use your account name in a URL like &lt;a href="https://myaccountname.signin.aws.amazon.com/console"&gt;https://myaccountname.signin.aws.amazon.com/console&lt;/a&gt; to populate the account id input with &lt;code&gt;myaccountname&lt;/code&gt; (that account alias must exist, otherwise you will land on a 404 page).&lt;/p&gt;

&lt;h2&gt;
  
  
  Definitions
&lt;/h2&gt;

&lt;p&gt;Now let's get to the definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account
&lt;/h3&gt;

&lt;p&gt;Think of an account as just a container. A container containing your resources, users and account settings.&lt;/p&gt;

&lt;p&gt;You can create multiple of these containers. In fact, it is best practice to separate your different workloads (for example test and production) into different accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root User
&lt;/h3&gt;

&lt;p&gt;The root user is the master of your account. It is the central key (this is why you should activate Multi-Factor Authentication (MFA) for it) and provides unrestricted access to the account. There is always exactly one root user per account.&lt;/p&gt;

&lt;p&gt;There are some &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/root-user-tasks.html"&gt;tasks that only a root user can do&lt;/a&gt;, e.g. changing the account settings (including the account name) and billing information.&lt;/p&gt;

&lt;p&gt;Again: use MFA for your root user and only use that user if you have to, don't use it for everyday work.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM User
&lt;/h3&gt;

&lt;p&gt;An IAM User is an entity representing a person or an application (it is not necessarily a person, this can be confusing because the word "user" implies a person). You create the IAM users after the initial creation of your root user. An account can have several users (think of people or applications being able to access the container of resources). &lt;/p&gt;

&lt;p&gt;There are various ways how you can grant IAM users permissions in order to be able to do something. One of them is attaching policies to the IAM user (&lt;a href="https://dev.topermissions%20for%20an%20IAM%20user"&gt;learn more about IAM permissions&lt;/a&gt;). An IAM user with the &lt;code&gt;AdministratorAccess&lt;/code&gt; policy attached is not the same as the root user.&lt;/p&gt;

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

&lt;p&gt;The concept of accounts and users in AWS is different from what we are used to (Google, Spotify, etc.). This can lead to confusion. Hopefully this post has cleared up that confusion for you.&lt;/p&gt;

&lt;p&gt;If you need help getting going, feel free to ask questions.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>When to (Not) Use React Context API for State?</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Fri, 24 Jun 2022 05:30:00 +0000</pubDate>
      <link>https://dev.to/jannikwempe/when-to-not-use-react-context-api-for-state-4g2n</link>
      <guid>https://dev.to/jannikwempe/when-to-not-use-react-context-api-for-state-4g2n</guid>
      <description>&lt;p&gt;&lt;a href="https://reactjs.org/docs/context.html" rel="noopener noreferrer"&gt;React's Context API&lt;/a&gt; is a popular choice for global state &lt;em&gt;(my definition: state that is shared amongst components)&lt;/em&gt;. It is easy to use and we are used to it because a lot of libraries leverage them. There are characteristics of React Context that you should be aware of. They make context not always the best choice for global state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does React Context Exist?
&lt;/h2&gt;

&lt;p&gt;Technically we could just place our whole state at our top-level component and pass it down the React element tree to the components that need access to the state. But in any application but a very simple one that would require us to pass down the state several levels down the tree and through components that are not using the state themselves at all. It would pollute the code and ruin the Developer Experience (DX). That problem is known as &lt;strong&gt;prop-drilling&lt;/strong&gt;. React's Context API was created to mitigate this issue. This is an excerpt from the &lt;a href="https://reactjs.org/docs/context.html" rel="noopener noreferrer"&gt;React Context API docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Context provides a way to pass data through the component tree without having to pass props down manually at every level.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By combining React's state-related hooks (&lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useReducer&lt;/code&gt;) with React context you can provide a shared state to all components nested within the contexts &lt;code&gt;Provider&lt;/code&gt;. Problem solved, right? Well, no. The context API has a major issue:&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue With Using React Context API for State
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Consumers of a context always re-render if the state provided by the context changes. It does not matter if a component actually uses the piece of the state that has changed.&lt;/strong&gt; Example: &lt;code&gt;ContextA&lt;/code&gt; provides the state &lt;code&gt;{ a: 1, b: 1 }&lt;/code&gt; and &lt;code&gt;ComponentA&lt;/code&gt; reads only &lt;code&gt;a&lt;/code&gt;. Even if only &lt;code&gt;b&lt;/code&gt; changes &lt;code&gt;ComponentA&lt;/code&gt; will re-render – for no reason, it will render the same content. This is called an extra or unnecessary re-render.&lt;/p&gt;

&lt;p&gt;For that reason, it is bad practice to have a single, huge state provided by a context. Instead, you should split the state up and create separate contexts like &lt;code&gt;AuthContext&lt;/code&gt;, &lt;code&gt;ThemeContext&lt;/code&gt;etc. Ask yourself if consumers mostly consume the majority of the state. Only in that case, you don't end up with a lot of extra re-renders. &lt;em&gt;(A few extra renders are not an issues at all but it can get out of control if a lot of components and their children re-render.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Besides the extra re-renders it can become hard to keep track of the data flow in your application. You won't be able to easily see which data is being used where, as it's the case with props. The React docs include a section &lt;a href="https://reactjs.org/docs/context.html#before-you-use-context" rel="noopener noreferrer"&gt;Before You Use Context&lt;/a&gt; for a reason. One highlighted excerpt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't get me wrong, the React Context API is a great tool. But don't see everything as a nail global state just because you have a hammer React's Context API.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use React's Context API?
&lt;/h2&gt;

&lt;p&gt;Now you may ask yourself when it is a good idea to use context for global state? I am glad you asked, this chart is my answer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1656014273759%2FRdKu5o_hW.png%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1656014273759%2FRdKu5o_hW.png%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="react-context-api-state.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, there are a lot of scenarios where other tools are preferable. I will explore a few of the alternatives in future posts.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Creating Reusable React Components with TypeScript</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Sun, 06 Mar 2022 09:49:51 +0000</pubDate>
      <link>https://dev.to/jannikwempe/creating-reusable-react-components-with-typescript-5gh5</link>
      <guid>https://dev.to/jannikwempe/creating-reusable-react-components-with-typescript-5gh5</guid>
      <description>&lt;p&gt;Often we write React components that get bigger and bigger and at some point, we extract parts of it into separate components. Either because the component is getting too big or because we need parts of it somewhere else.&lt;/p&gt;

&lt;p&gt;This is generally a good approach but after a while, we can up with several components that are similar (e.g. some kind of list, card, or whatever). Often they have some similarities. Wouldn't it be nice to have some basic building blocks that can be reused in order to encapsulate such similarities?&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to use the React techniques "render props" and "as-prop" and how to use them with TypeScript.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://github.com/JannikWempe/react-reusable-components"&gt;finished code on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Point: Non-Generic JS Component
&lt;/h2&gt;

&lt;p&gt;This is the component we are starting with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../api/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// Pokemon is { name: string; url: string; }&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PokemonList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pokemons&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noreferrer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: I know that "Pokemons" isn't the plural, but I use the "s" to distinguish it from the singular.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is just a list of Pokemon which is rendering some links – nothing special here. But imagine at another place we are either creating a similar list with trainers or a list which is containing more information about the Pokemon.&lt;/p&gt;

&lt;p&gt;We could come with a &lt;code&gt;&amp;lt;LinkList /&amp;gt;&lt;/code&gt; which is also usable for trainers or add an optional prop to this component indicating that it should also render more details. But these solutions aren't really reusable either. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 Boolean flags like &lt;code&gt;showDetails&lt;/code&gt; are often a code smell. It indicates that the component is doing more than one thing – it is violating the &lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle"&gt;Single Responsibility Principle (SRP)&lt;/a&gt;. That is not only true for React components, but for functions in general.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Okay, let us create a truly reusable &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Render Props
&lt;/h2&gt;

&lt;p&gt;First of all, what is the React Render Props technique? It is even mentioned in the official React docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.&lt;/p&gt;

&lt;p&gt;A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Render props are a way to achieve &lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control"&gt;Inversion of Control (IaC)&lt;/a&gt;. Instead of having the child component control the rendering of the list items, we reverse control and have the parent component control the rendering of the items.&lt;/p&gt;

&lt;p&gt;This is what a generic &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; component could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderItem&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the component now has no reference to Pokemon anymore. It could render anything, no matter if Pokemon, trainers, or something else.&lt;/p&gt;

&lt;p&gt;It not only is a generic component not, but it also uses a TypeScript generic for the component props. We use the generic &lt;code&gt;Item&lt;/code&gt; type for the list of &lt;code&gt;items&lt;/code&gt;and for the single &lt;code&gt;item&lt;/code&gt;. When we pass one of the props to this component, if we use it somewhere, React (or rather TypeScript) knows that the other prop has the same type.&lt;/p&gt;

&lt;p&gt;This is how we would use it to achieve the same output as in our initial example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/List&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPokemons&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./api/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pokemons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getPokemons&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// returns some fix dummy data&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemons&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noreferrer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we pass &lt;code&gt;items&lt;/code&gt;, which is of type &lt;code&gt;Pokemon[]&lt;/code&gt; first, then the single item in &lt;code&gt;renderItem&lt;/code&gt; is inferred to be &lt;code&gt;Pokemon&lt;/code&gt;. We could also pass the single item in &lt;code&gt;renderItem&lt;/code&gt; first, but in that case, we have to explicitly type it like &lt;code&gt;renderItem={(pokemon: Pokemon) =&amp;gt; (&lt;/code&gt;. Now, &lt;code&gt;items&lt;/code&gt; must be of type &lt;code&gt;Pokemon[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now the parent controls how the items of the list are rendered. That is nice, but it has a major flaw: &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; renders an outer &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; and therefore we must return an &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; from &lt;code&gt;renderItem&lt;/code&gt; in order to end up with valid HTML. We would have to remember that and we can't use it for more generic lists where we don't want to use an &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; at all. This is where the as-prop comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  React As Prop
&lt;/h2&gt;

&lt;p&gt;Our goal: We not only want to reverse control over how a single item is rendered but also for the HTML tag used by &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt;. We can achieve that with the as-prop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;as&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;as&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the parent component can decide what HTML tag &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; renders. It could also make it not render an outer tag at all by passing &lt;code&gt;as&lt;/code&gt; like this &lt;code&gt;&amp;lt;List as={React.Fragment} /&amp;gt;&lt;/code&gt;. Per default &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; renders an &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; tag. Therefore our current usage in the parent doesn't have to change at all.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: We can't just use the &lt;code&gt;as&lt;/code&gt; prop like &lt;code&gt;&amp;lt;as&amp;gt;content&amp;lt;/as&amp;gt;&lt;/code&gt; because that would not be valid JSX. Non-native HTML tags have to be uppercased. You could uppercase the &lt;code&gt;As&lt;/code&gt; prop in the first place but I personally find it quite awkward.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is still one caveat. If we decide to render an outer &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;img&lt;/code&gt; tag (which we probably won't in our example, but it is very relevant when dealing with the as-prop in general), then we can't pass required props like &lt;code&gt;href&lt;/code&gt; or &lt;code&gt;src&lt;/code&gt; to &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt;. Not only TypeScript would complain, but also the props would not be forwarded to the &lt;code&gt;&amp;lt;Component /&amp;gt;&lt;/code&gt;within &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt;. This is how we can deal with that (this is the final version):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;as&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ComponentPropsWithoutRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;As&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now pass all props besides &lt;code&gt;items&lt;/code&gt;, &lt;code&gt;renderItem&lt;/code&gt; and &lt;code&gt;as&lt;/code&gt; to &lt;code&gt;Component&lt;/code&gt; by using the spread operator for &lt;code&gt;rest&lt;/code&gt;. Now we technically could pass an &lt;code&gt;href&lt;/code&gt; from the parent component, but TypeScript would still complain. We can solve this with &lt;code&gt;React.ComponentPropsWithoutRef&amp;lt;As&amp;gt;&lt;/code&gt;, which results – as the name already implies – in all prop types of the &lt;code&gt;As&lt;/code&gt; component excluding the &lt;code&gt;ref&lt;/code&gt; prop. If we would now pass &lt;code&gt;as={"a"}&lt;/code&gt;, TypeScript autocomplete would suggest props from an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag, like &lt;code&gt;href&lt;/code&gt; to us.&lt;/p&gt;

&lt;p&gt;What is &lt;code&gt;Omit&amp;lt;React.ComponentPropsWithoutRef&amp;lt;As&amp;gt;, keyof Props&amp;lt;Item, As&amp;gt;&amp;gt;&lt;/code&gt; doing here? If we would include something like &lt;code&gt;href: MyHrefType&lt;/code&gt; in our &lt;code&gt;Props&lt;/code&gt; type and use &lt;code&gt;as="a"&lt;/code&gt;, then we would end up with an error when trying to pass any &lt;code&gt;href&lt;/code&gt;: &lt;code&gt;Type 'string' is not assignable to type 'never'.&lt;/code&gt;. &lt;code&gt;Omit&lt;/code&gt; excludes all prop types that we explicitly defined in our &lt;code&gt;Props&lt;/code&gt; type from the result of &lt;code&gt;React.ComponentPropsWithoutRef&amp;lt;As&amp;gt;&lt;/code&gt;. In our case – passing &lt;code&gt;as="a"&lt;/code&gt; – &lt;code&gt;Omit&amp;lt;React.ComponentPropsWithoutRef&amp;lt;As&amp;gt;, keyof Props&amp;lt;Item, As&amp;gt;&amp;gt;&lt;/code&gt; would not include the &lt;code&gt;href&lt;/code&gt; type anymore. We now can pass the &lt;code&gt;href&lt;/code&gt; prop of type &lt;code&gt;MyHrefType&lt;/code&gt; again. &lt;strong&gt;TLDR;&lt;/strong&gt; it deduplicates types.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Now our &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; is truly generic and reusable for a lot of cases. I often still prefer creating something like a &lt;code&gt;&amp;lt;PokemonList /&amp;gt;&lt;/code&gt; which uses the &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; as a building block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../api/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./List&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PokemonList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pokemons&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemons&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noreferrer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can easily create something like &lt;code&gt;&amp;lt;PokemonDetailsList /&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;TrainersList /&amp;gt;&lt;/code&gt; or whatever – or use the &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; directly.&lt;/p&gt;

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

&lt;p&gt;React techniques like render props and the as prop enable us to build our own reusable, generic building blocks. Typing those generic components isn't that easy (at least this is how I feel about it). Therefore we also learned how to type those generic components using TypeScript.&lt;/p&gt;

&lt;p&gt;I admit that this &lt;code&gt;&amp;lt;List /&amp;gt;&lt;/code&gt; component is a contrived example as it seems to offer not that many benefits compared to our initial solution. But the techniques that I have shown here are very relevant and the simple example allowed me to focus on the techniques. These techniques are widely used in libraries like &lt;a href="https://chakra-ui.com/"&gt;Chakra UI&lt;/a&gt; and &lt;a href="https://headlessui.dev/"&gt;headless UI&lt;/a&gt; which I really enjoy using.&lt;/p&gt;

&lt;p&gt;There are many other techniques for creating reusable React components. Some of them utilize React context and composition (rather than inheritance). These techniques might be the topics for future articles.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>My Favorite Tech Stack for 2022</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Thu, 23 Dec 2021 19:31:51 +0000</pubDate>
      <link>https://dev.to/jannikwempe/my-favorite-tech-stack-for-2022-1d3</link>
      <guid>https://dev.to/jannikwempe/my-favorite-tech-stack-for-2022-1d3</guid>
      <description>&lt;p&gt;I just recently tweeted my favorite tech stack for 2022 (inspired by &lt;a href="https://twitter.com/jonmeyers_io" rel="noopener noreferrer"&gt;@jonmeyers_io&lt;/a&gt; tweet). I'd like to share some more thoughts about my choices in this post.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1473935933447839748-733" src="https://platform.twitter.com/embed/Tweet.html?id=1473935933447839748"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1473935933447839748-733');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1473935933447839748&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;First of all: I love frontend development. It is the direct touchpoint to the user for websites / -apps. It is the first impression for the user.&lt;/p&gt;

&lt;p&gt;There is so much out there. It can be paralyzing. I already used a variety of frontend frameworks: React (CRA, Gatsby, NextJS), Vue, Angular, Svelte (SvelteKit). And as far as styling goes, I've tried a lot of things too: CSS (modules), SASS, CSS-in-JS, Material, Bootstrap, Bulma, Quasar, Tailwind, Chakra UI, and more. Therefore you can assume I've tried quite a lot and my choices are not the only ones I know. (Not saying that other tools don't do the job and are inferior. It also comes down to personal preference.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Svelte / SvelteKit
&lt;/h3&gt;

&lt;p&gt;This blog post goes into detail &lt;a href="https://blog.jannikwempe.com/why-svelte-is-different-and-awesome" rel="noopener noreferrer"&gt;Why Svelte is different - and awesome&lt;/a&gt;. I just really enjoy using Svelte. It is more concise than React and more performant. Stores and animations are also great features. There is a reason why Svelte was the &lt;a href="https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-web-frameworks" rel="noopener noreferrer"&gt;most loved web framework in the Stack Overflow Developer Survey 2021&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think Svelte will make a jump in popularity with the release of SvelteKit version 1.0 which is my default for every Svelte app. In addition to that, Rich Harris (the creator of Svelte) was hired by Vercel and is now working full-time on Svelte / SvelteKit. &lt;/p&gt;

&lt;p&gt;Svelte will rise and shine ✨&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Learn more about Svelte&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kit.svelte.dev/" rel="noopener noreferrer"&gt;Learn more about SvelteKit&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NextJS
&lt;/h3&gt;

&lt;p&gt;Currently, I am still often using NextJS. It is great! Just like SvelteKit is my default for every Svelte project, NextJS is my default for any React project. Mostly for the same reasons: Static Site Generation (SSG), Server-Side Rendering (SSR), built-in file-based routing based, and more. &lt;/p&gt;

&lt;p&gt;The ecosystem for React is way larger than the Svelte one and more people are familiar with React. Therefore NextJS is my choice for working together with other React-devs and when relying on a certain library that is not (yet) available in Svelte (can't think of any out of my head). In addition, the demand and job market for React / NextJS are much larger than for Svelte / SvelteKit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Learn more about NextJS&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TailwindCSS
&lt;/h3&gt;

&lt;p&gt;I love styling with utilities based on a predefined and easily customizable theme. If you have read my post &lt;a href="https://blog.jannikwempe.com/debunking-tailwind-counterarguments" rel="noopener noreferrer"&gt;Debunking Tailwind Counterarguments&lt;/a&gt; you already know that I am a big fan. Most often I use &lt;a href="https://headlessui.dev/" rel="noopener noreferrer"&gt;Headless UI&lt;/a&gt; as an addition to get some functionality like a select or a modal. I've also bought &lt;a href="https://tailwindui.com/" rel="noopener noreferrer"&gt;Tailwind UI&lt;/a&gt; in order to move faster and also for some inspiration — and I don't regret it.&lt;/p&gt;

&lt;p&gt;I just can't go back to UI libraries like Material UI or Bootstrap anymore 🤷🏼‍♂️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Learn more about TailwindCSS&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Chakra UI
&lt;/h3&gt;

&lt;p&gt;Chakra UI is inspired by Tailwind. It is also based on a theme that uses very similar design tokens. The difference to TailwindCSS is that it comes with a lot of components (therefore it is framework-specific; originally created for React but also available for Vue). The components are created with accessibility in mind. Chakra UI feels like a headstart compared to Tailwind when initially getting started but it also is a little less flexible (framework-specific, peer dependencies, etc.) I love both!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chakra-ui.com/" rel="noopener noreferrer"&gt;Learn more about Chakra UI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;No frontend without backend (at least if you also consider static site hosting as backend). I do not only love frontend but I love backend as well — yes, I know, focusing is not one of my strengths, but I just can't go with just one of them. &lt;/p&gt;

&lt;h3&gt;
  
  
  Vercel
&lt;/h3&gt;

&lt;p&gt;Vercel is my go-to for hosting my projects. It just provides a great Developer Experience (DX). Luckily they are not only the creators of NextJS but now also have Rich Harris and therefore SvelteKit expertise on board.&lt;/p&gt;

&lt;p&gt;For some of my projects, Vercel alone is sufficient as it also provides also server-side functions. If it is not sufficient and I just need a little more, like auth, a DB or some storage, I go with Supabase next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com" rel="noopener noreferrer"&gt;Learn more about Vercel&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Supabase
&lt;/h3&gt;

&lt;p&gt;Supabase ("The Open SourceFirebase Alternative") is great. It has a great DX, is very easy to use while also being quite powerful, and has a generous free tier (and is also quite cheap beyond that).&lt;/p&gt;

&lt;p&gt;Supabase will be sufficient for a lot of use cases as it provides auth, a DB with a good API through their SDK and storage. If it is not sufficient, I go with AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Learn more about Supabase&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS CDK / Serverless Framework
&lt;/h3&gt;

&lt;p&gt;There is literally nothing you can't do with AWS. In addition, AWS skills make you very attractive on the job market (my LinkedIn inbox is pretty full since I earned the AWS Associate Developer certificate).&lt;/p&gt;

&lt;p&gt;I've used Cloudformation, SAM, CDK and the Serverless Framework so far. I can't really decide between CDK and Serverless. I like writing my infrastructure in TypeScript but I also appreciate the ease of use and plugin system of Serverless. Both of them are well suited for serverless architectures which is what I personally almost exclusively use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;Learn more about AWS CDK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Learn more about Serverless Framework&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;That's it. Nothing highly sophisticated. It is mostly the tech I enjoy and I think is valuable in the future. There are also other libraries I really enjoy, like &lt;a href="https://xstate.js.org/" rel="noopener noreferrer"&gt;XState&lt;/a&gt; and &lt;a href="https://react-query.tanstack.com/" rel="noopener noreferrer"&gt;React Query&lt;/a&gt; (there is also &lt;a href="https://sveltequery.vercel.app/" rel="noopener noreferrer"&gt;Svelte Query&lt;/a&gt;). Just to mention a few.&lt;/p&gt;

&lt;p&gt;How does your go-to stack in 2021 look like?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>fullstack</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Authentication: Cookie- vs. Token-based</title>
      <dc:creator>Jannik Wempe</dc:creator>
      <pubDate>Sun, 28 Nov 2021 18:27:41 +0000</pubDate>
      <link>https://dev.to/jannikwempe/authentication-cookie-vs-token-based-cgn</link>
      <guid>https://dev.to/jannikwempe/authentication-cookie-vs-token-based-cgn</guid>
      <description>&lt;p&gt;Authentication is about confirming that users are who they say they are. Whereas authorization is about permissions of a given user (e.g. admin vs. user). Authentication is an integral part of most apps.&lt;/p&gt;

&lt;p&gt;The two main methods for authentication are cookies and tokens. But what are the differences between the cookie- and the token-based approaches?&lt;/p&gt;

&lt;h2&gt;
  
  
  Cookie-based Authentication
&lt;/h2&gt;

&lt;p&gt;The cookie-based approach is also often referred to as &lt;strong&gt;session authentication&lt;/strong&gt;. When using session authentication, a cookie with the session id is created on the server and is sent to the client. The browser automatically stores the cookie and sends it alongside every subsequent request to the server. The server then looks up the session id and verifies its validity. The client doesn't have to deal with storing session-related information at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sidenote:&lt;/strong&gt; Dealing with sessions by using a cookie is not the same as a &lt;strong&gt;session cookie&lt;/strong&gt;. A session cookie is a cookie without the &lt;code&gt;Max-Age&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt; attribute being set. Therefore a session cookie gets deleted when a user closes the browser window or tab (= a user is ending the session). The term session cookie provides no information about what content a cookie stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token-based Authentication
&lt;/h2&gt;

&lt;p&gt;Tokens use a different approach. The token containing the session information is created on the server. It is encoded and signed by the server and sent to the client. The client can use the session information in that token. In this case, the client has to store the token (usually in &lt;code&gt;localStorage&lt;/code&gt; or &lt;code&gt;sessionStorage&lt;/code&gt;) and has to actively send the token along with every request (usually in the &lt;code&gt;Authorization&lt;/code&gt; header). The server doesn't have to keep track of the sessions. The token contains all information the server needs to verify the session. (&lt;strong&gt;EDIT:&lt;/strong&gt; Except the secret key, which is used for signing.) The signing of the token prevents the client from manipulating it.&lt;/p&gt;

&lt;p&gt;The most popular way of using tokens for authentication is JSON web tokens (JWTs). You can learn more about JWTs specifically at &lt;a href="https://jwt.io/introduction"&gt;jwt.io&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The main difference between the cookie and token-based approach is where the session information is stored. In the cookie-based approach, the burden of storing the session is on the server-side in contrast to the token-based approach where the client is responsible for storing the session information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--665TexD1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1636403503161/8wg6Rz35A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--665TexD1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1636403503161/8wg6Rz35A.png" alt="authentication.png" width="780" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>authentication</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
