<?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: Thomas Step</title>
    <description>The latest articles on DEV Community by Thomas Step (@thomasstep).</description>
    <link>https://dev.to/thomasstep</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%2F155653%2Fe67c594f-0ab5-4e51-92a6-32fdee7c9d98.jpeg</url>
      <title>DEV Community: Thomas Step</title>
      <link>https://dev.to/thomasstep</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thomasstep"/>
    <language>en</language>
    <item>
      <title>How to Create a Custom Error Class in Javascript</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Thu, 27 Jan 2022 09:00:09 +0000</pubDate>
      <link>https://dev.to/thomasstep/how-to-create-a-custom-error-class-in-javascript-15hm</link>
      <guid>https://dev.to/thomasstep/how-to-create-a-custom-error-class-in-javascript-15hm</guid>
      <description>&lt;p&gt;This is going to be short and sweet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&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;message&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;message&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyError&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;MyError&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;You can throw the error like so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./errors&lt;/span&gt;&lt;span class="dl"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is my error that threw.&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;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Instance of MyError found.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;



</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>How I Implement Hexagonal Architecture in AWS Lambda</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Thu, 27 Jan 2022 09:00:09 +0000</pubDate>
      <link>https://dev.to/thomasstep/how-i-implement-hexagonal-architecture-in-aws-lambda-2430</link>
      <guid>https://dev.to/thomasstep/how-i-implement-hexagonal-architecture-in-aws-lambda-2430</guid>
      <description>&lt;p&gt;At re:Invent 2021, I learned about hexagonal architecture. I am a curious person so I started looking further into it. Like most architecture concepts, I could not find many examples of hexagonal architecture implementations. I ran back into my own &lt;a href="https://thomasstep.com/blog/reinvent-evolutionary-aws-lambda-functions-with-hexagonal-architecture"&gt;article about hexagonal architecture&lt;/a&gt; or some copy and pasted content farm's article which took some buzzwords from &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/"&gt;Alistair Cockburn's original post&lt;/a&gt; and made it their own.&lt;/p&gt;

&lt;p&gt;Fast forward to this week and I stumbled across a video on YouTube titled &lt;a href="https://www.youtube.com/watch?v=qZEMSK6S0QM"&gt;Hexagonal Architecture by Example&lt;/a&gt;. This is what I was originally looking for, and it spurred me on to finish out my understanding of this architecture. The video is helpful to watch through, but the &lt;a href="https://github.com/onicagroup/hexagonal-example"&gt;GitHub repo&lt;/a&gt; they made available was my fast track to a better understanding. Having the good and bad versions of the same code right next to each other was a key for me. They also used slightly different names than the &lt;a href="https://github.com/aws-samples/aws-Lambda-hexagonal-architecture"&gt;code base demoed in the re:Invent talk&lt;/a&gt; that I attended.&lt;/p&gt;

&lt;p&gt;While I had never heard of &lt;code&gt;domains&lt;/code&gt;, &lt;code&gt;ports&lt;/code&gt;, and &lt;code&gt;adapters&lt;/code&gt;, I did know about the &lt;code&gt;handlers&lt;/code&gt; and &lt;code&gt;repositories&lt;/code&gt; used in this newly found repo, which brings me to my first point of understanding: the naming is mostly arbitrary, the use and context boundaries are what matter. Both of these codebases did the same thing, but with slightly different naming. The idea was that a request could come into an &lt;code&gt;adapter&lt;/code&gt;, that &lt;code&gt;adapter&lt;/code&gt; would then communicate with business logic (&lt;code&gt;domain&lt;/code&gt;) through a &lt;code&gt;port&lt;/code&gt;. If the business logic needed to initiate communication with anything outside of its boundaries, then it would also communicate through a &lt;code&gt;port&lt;/code&gt; to another &lt;code&gt;adapter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While I was able to start reading and understanding the code in both repos and understanding where the context boundaries lied, I was still slightly confused about why we needed &lt;code&gt;port&lt;/code&gt;s at all. Both repos' examples showed &lt;code&gt;port&lt;/code&gt;s as simply a passthrough, so why not just have an &lt;code&gt;adapter&lt;/code&gt; call the business logic and the business logic call &lt;code&gt;adapter&lt;/code&gt;s? The answer was something I had seen over and over again in the software engineering world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design"&gt;&lt;code&gt;SOLID&lt;/code&gt;&lt;/a&gt; is a commonly used acronym which gives us principles for OOP. The &lt;code&gt;D&lt;/code&gt; stands for &lt;a href="https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design#dependency-inversion-principle"&gt;dependency inversion priciple&lt;/a&gt;, which means that code should rely on abstractions not code that implements the functionality. In the context of hexagonal architecture, &lt;code&gt;port&lt;/code&gt;s are the abstraction and &lt;code&gt;adapter&lt;/code&gt;s and business logic is the code that implements the functionality. A &lt;code&gt;port&lt;/code&gt; is commonly implemented using an interface, but it is a conceptually a contract between an &lt;code&gt;adapter&lt;/code&gt; and business logic for requests and responses similar to an &lt;a href="https://thomasstep.com/blog/api-gateway-models"&gt;API model&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With my newfound understanding of hexagonal architecture, I looked back at some of my past projects and wanted to see how far off I was from implementing the architecture. It turns out that I was not far off. We can take a peek at a smaller Lambda real quick to get an idea of what I used to have, then I will refactor my code to fit a hexagonal architecture and highlight the changes I make along the way. For a slight bit of context, this code is a Lambda handler for a calendar API that I wrote. This particular endpoint creates an event for a calendar under the path &lt;code&gt;/calendars/{calendarId}/events&lt;/code&gt; where &lt;code&gt;{calendarId}&lt;/code&gt; is replaced by the user as the ID for a previously created calendar that they would like to modify.&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="cm"&gt;/**
 * File: index.js
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/database/events&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;getDateRange&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/getDateRange&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;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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calendarId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calendarId&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create Dates&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;endTime&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="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;endTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;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;startTime and endTime are not ISO 8601 format.&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;errorPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startTime and endTime must be ISO 8601 timestamps e.g. 1995-12-13T03:24:00Z.&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;return&lt;/span&gt; &lt;span class="nx"&gt;errorPayload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check start comes after end&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;endTime&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;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;startTime is not earlier than endTime.&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;errorPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startTime must come before endTime.&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;return&lt;/span&gt; &lt;span class="nx"&gt;errorPayload&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;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getDateRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endTime&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;eventId&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;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uncaughtError&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uncaughtError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;uncaughtError&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="cm"&gt;/**
 * File: /opt/database/events.js
 */&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;documentClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./databaseSession&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;generateToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../generateToken&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="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateToken&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;batchWritePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="c1"&gt;// Construct batchWritePayload&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;batchWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchWritePayload&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;eventId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;create&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;Luckily enough, I already had my database functionality completely separate from my business logic due to a &lt;a href="https://thomasstep.com/blog/example-for-using-the-single-responsibility-principal"&gt;past experience&lt;/a&gt;. However, the database calls are the code implementing functionality, so my business logic should be communicating with it through an abstraction (&lt;code&gt;port&lt;/code&gt;) not directly. My Lambda's business logic also knows about my data model and that I am using DynamoDB because the &lt;code&gt;getDateRange&lt;/code&gt; call would only be used for a NoSQL database. (This is an implementation detail, and I do not believe that it is necessary for understanding the refactor. The main point is that my business logic knows about database-specific implementation details.)&lt;/p&gt;

&lt;p&gt;My incoming &lt;code&gt;adapter&lt;/code&gt; would be what transforms the Lambda-function-specific call into the parameters needed for my business logic to run and then communicate through a &lt;code&gt;port&lt;/code&gt; to my business logic. Currently, there is no incoming &lt;code&gt;adapter&lt;/code&gt; or &lt;code&gt;port&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, let's tackle the incoming &lt;code&gt;adapter&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * File: index.js
 */&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;InputError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/errors&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;GOOD_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BAD_INPUT_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SERVER_ERROR_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/logger&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;port&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calendarId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calendarId&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&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;port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOOD_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SERVER_ERROR_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal server error&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;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;InputError&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;BAD_INPUT_STATUS_CODE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errorPayload&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="cm"&gt;/**
 * File: port.js
 */&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;InputError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/errors&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;logic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Validate input format&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;endTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;InputError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startTime and endTime must be ISO 8601 timestamps e.g. 1995-12-13T03:24:00Z.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Construct event based on contract&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endTime&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;eventId&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;logic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;eventId&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;While going about this refactor I decided to leave the Lambda configuration completely intact and only move code, so the incoming &lt;code&gt;adapter&lt;/code&gt; will remain as the &lt;code&gt;index.handler&lt;/code&gt;. Since this is the incoming &lt;code&gt;adapter&lt;/code&gt; all it needs to do is parse out the required parameters for the &lt;code&gt;port&lt;/code&gt;, call the &lt;code&gt;port&lt;/code&gt;, and then return to the client. That is all it does. The other additions used to clean up this code were the &lt;code&gt;InputError&lt;/code&gt; and &lt;code&gt;*_STATUS_CODE&lt;/code&gt; constants. I wanted to be able to throw and handle custom errors as well as get rid of "magic numbers."&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;port&lt;/code&gt; could have been an easy pass-through, but I also wanted to add a bit of validation before throwing the parameters at my business logic. The &lt;code&gt;Date&lt;/code&gt; creation validation was present in the old handler. Constructing the &lt;code&gt;event&lt;/code&gt; based on my contract with the &lt;code&gt;logic&lt;/code&gt; function might be a little overkill since I also have a model set up for this endpoint in my API Gateway but I am going for reusability and proper abstraction. Completely validating and constructing the &lt;code&gt;event&lt;/code&gt; for &lt;code&gt;logic&lt;/code&gt; ensures that whatever is calling my &lt;code&gt;port&lt;/code&gt; abstraction does not matter. This &lt;code&gt;port&lt;/code&gt; should take care of any details thus becoming a proper abstraction.&lt;/p&gt;

&lt;p&gt;The next chunk of code I looked at was the business logic itself.&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="cm"&gt;/**
 * File: logic.js
 */&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;InputError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/errors&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;createEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/ports&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;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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Business logic
 * @param {string} calendarId
 * @param {Object} event
 * @param {string} event.title
 * @param {string} event.startTime ISO-8601 format
 * @param {string} event.endTime ISO-8601 format
 * @returns {string}
 */&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logic&lt;/span&gt; &lt;span class="o"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Check that start comes before end&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&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;endTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endTime&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;endTime&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="nx"&gt;InputError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startTime must come before endTime.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&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;createEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;eventId&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 I could move the &lt;code&gt;Date&lt;/code&gt; creation validation out since &lt;code&gt;logic&lt;/code&gt; now knows that it will be called with a properly formatted &lt;code&gt;event.startTime&lt;/code&gt; and &lt;code&gt;event.endTime&lt;/code&gt;. If I were using Typescript, I would have created an &lt;code&gt;interface&lt;/code&gt;. Using Javascript means that I need to hold myself a little more accountable and enforce the contract in code on my own. &lt;code&gt;logic&lt;/code&gt; only deals with value-specific validation since it can know the contract has been met, which we see with the start and end time comparisons. It also no longer deals with the database implementation details, but rather passes the relevant information on and lets the database &lt;code&gt;adapter&lt;/code&gt; deal with constructing the data model.&lt;/p&gt;

&lt;p&gt;Lastly, I refactored my outgoing/database &lt;code&gt;port&lt;/code&gt; and &lt;code&gt;adapter&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * File: /opt/ports.js
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/database/events&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;getDateRange&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/opt/getDateRange&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="nx"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getDateRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endTime&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;eventId&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;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * File: /opt/database/events.js
 */&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;documentClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./databaseSession&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;generateToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../generateToken&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="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateToken&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;batchWritePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="c1"&gt;// Construct batchWritePayload&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;batchWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchWritePayload&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;eventId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;create&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;Notice anything? The code in &lt;code&gt;/opt/database/events.js&lt;/code&gt; did not have the change. As I said, I luckily had the database functionality properly abstracted, the problem was that my business logic called the database functionality instead of an abstraction (the database &lt;code&gt;port&lt;/code&gt;) to the functionality. This made the refactor for my database &lt;code&gt;port&lt;/code&gt; and &lt;code&gt;adapter&lt;/code&gt; simple. All I needed to do was move the &lt;code&gt;getDateRange&lt;/code&gt; call outside of my business logic into my &lt;code&gt;port&lt;/code&gt; and have my &lt;code&gt;port&lt;/code&gt; call the database &lt;code&gt;adapter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Well, there we have it. I have been wanting to implement a hexagonal architecture, especially in Lambda functions, since I attended that talk at re:Invent a few months ago. After finding another few key resources on implementation, I am now much more confident that I understand the details and boundaries. As I have said in plenty of my other posts, I use kaizen in many aspects of my life. This is only my first pass at implementing a hexagonal architecture so take it with a grain of salt. I have implemented everything to the best of my knowledge but I am sure I will learn new and better ways in the future. For now, I hope this has helped someone else jump to implementation understanding quicker. Happy coding.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>node</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How To Write AWS CDK Tests</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Sat, 22 Jan 2022 09:00:09 +0000</pubDate>
      <link>https://dev.to/thomasstep/how-to-write-aws-cdk-tests-h18</link>
      <guid>https://dev.to/thomasstep/how-to-write-aws-cdk-tests-h18</guid>
      <description>&lt;p&gt;Writing tests is a necessary nuisance. I would much rather spend my time writing functionality than writing code to verify what I wrote but mistakes happen and backward compatibility needs to be preserved. After I have finished writing tests I always feel much better about a codebase. For some reason, writing that code is simply not as enjoyable. That is one of the reasons why I dragged my feet for so long on writing tests for &lt;code&gt;crow-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another big reason I took so long to write tests is that testing infrastructure is a fairly new thing. How can we test a VM's configuration that was spun up by a different team in a data center that is homebrewed? Those scripts would also need to be tailored and probably not worth the effort. It would most likely be easier to write E2E or integration tests after code has been deployed onto the servers. I did not expect to find many resources online about testing CDK Constructs and Stacks simply because I figured it was new enough.&lt;/p&gt;

&lt;p&gt;My assumption of a lack of documentation and examples was not too far off, but like the inspiration for many of my posts, I would like to contribute something back that I found missing.&lt;/p&gt;

&lt;p&gt;Whenever we start a new CDK app, the tool automatically creates a &lt;code&gt;test/&lt;/code&gt; folder, installs &lt;code&gt;jest&lt;/code&gt;, and gives us a little bit of boilerplate code. AWS obviously wants us to write tests. I did find a serious lack of official documentation around the tools the CDK has to write tests. The &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions-readme.html"&gt;&lt;code&gt;aws-cdk-lib.assertions&lt;/code&gt; module&lt;/a&gt; is about all I could find (someone please point me in the correct direction if I overlooked something). Even though that documentation ended up being about all I needed, it was still discouraging to not find much else.&lt;/p&gt;

&lt;p&gt;The boilerplate code given to us with a fresh CDK app looks like the following.&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="c1"&gt;// WHEN&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stack&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;ScheduleApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScheduleApiStack&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;MyTestStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// THEN&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::SQS::Queue&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;VisibilityTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line should look familiar (&lt;code&gt;const app = new cdk.App();&lt;/code&gt;) because it is the same as initializing an app whenever we want to deploy something. The stack creation is the same as well, &lt;code&gt;const stack = new ScheduleApi.ScheduleApiStack(app, 'MyTestStack');&lt;/code&gt;. Once we get to &lt;code&gt;const template = Template.fromStack(stack);&lt;/code&gt; things start diverging. What I say from here on out is based on my best knowledge at the time of writing. It is possible that I am not following best practices, but I can not find anything about best practices.&lt;/p&gt;

&lt;p&gt;It seems to me like the best way to test CDK code is to synthesize the code into CloudFormation stacks then run assertions against the huge string that is the template. This is what the boilerplate code that the CDK generates shows and the &lt;code&gt;aws-cdk-lib.assertions&lt;/code&gt; module shows no other way of testing anything. This means that the &lt;code&gt;props&lt;/code&gt; given to the stacks used in tests should be identical to the &lt;code&gt;props&lt;/code&gt; given to the stacks being deployed to correctly test configuration.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Template&lt;/code&gt; created from running &lt;code&gt;Template.fromStack()&lt;/code&gt; can then be queried for resources, mappings, and outputs using the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions.Template.html#methods"&gt;&lt;code&gt;Template&lt;/code&gt; class's methods&lt;/a&gt;. The methods starting with &lt;code&gt;has&lt;/code&gt; will throw errors if the corresponding resource in the template is not found, and the methods starting with &lt;code&gt;find&lt;/code&gt; will return the resources themselves as well as their logical IDs.&lt;/p&gt;

&lt;p&gt;I am going to show some examples from the tests that I wrote for &lt;code&gt;crow-api&lt;/code&gt;. (These tests might change but the exact &lt;a href="https://github.com/thomasstep/crow-api/blob/27b8cbebfafc0c98f78541ee747babaef63dc5b4/test/crow-api.test.ts"&gt;commit's file I am referencing is here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;One of the &lt;a href="https://github.com/thomasstep/crow-api/blob/27b8cbebfafc0c98f78541ee747babaef63dc5b4/test/crow-api.test.ts#L132-L134"&gt;first and most straightforward tests&lt;/a&gt; I wrote looks like the following.&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;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::ApiGateway::RestApi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;testing-crow-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This call is simply asserting that the template contains a &lt;code&gt;RestApi&lt;/code&gt; resource with the &lt;code&gt;Name&lt;/code&gt; property set to &lt;code&gt;testing-crow-api&lt;/code&gt;. Notice that the &lt;code&gt;Name&lt;/code&gt; property is referencing the naming from the &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-name"&gt;CloudFormation template&lt;/a&gt; not the prop from the CDK code (&lt;code&gt;restApiName&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The next tests that I wrote started getting more complicated. I wanted to start testing that the API Gateway &lt;code&gt;Resource&lt;/code&gt;s were pointing towards the correct parents. With CDK this is simple, but there is more going on under the covers to make the CloudFormation work. A resource's logical ID is referenced in the CloudFormation template, but with CDK code, we do not interface with logical IDs. The question then turns into a matter of teasing the logical ID out of the CDK stack or the &lt;code&gt;Template&lt;/code&gt;. For this &lt;a href="https://github.com/thomasstep/crow-api/blob/main/test/crow-api.test.ts#L138-L149"&gt;first example&lt;/a&gt;, I was able to grab the ID from the CDK stack.&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;function&lt;/span&gt; &lt;span class="nx"&gt;getLogicalId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&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;Stack&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;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IResource&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;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getLogicalId&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&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;CfnElement&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;restApiLogicalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getLogicalId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::ApiGateway::Resource&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;ParentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;restApiLogicalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RootResourceId&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="na"&gt;PathPart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;restApiLogicalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/thomasstep/crow-api/blob/27b8cbebfafc0c98f78541ee747babaef63dc5b4/test/crow-api.test.ts#L301-L330"&gt;next example&lt;/a&gt; become slightly more complicated. I first needed to use &lt;code&gt;Template&lt;/code&gt;'s &lt;code&gt;findResources&lt;/code&gt; using properties unique to a specific resource, then grab the logical ID from the result of the &lt;code&gt;findResources&lt;/code&gt; call, and finally use the logical ID in a &lt;code&gt;hasResourceProperties&lt;/code&gt; call.&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;function&lt;/span&gt; &lt;span class="nx"&gt;logicalIdFromResource&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="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;Resource is not unique.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;logicalId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resKeys&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;logicalId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorsPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::ApiGateway::Resource&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;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;PathPart&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="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;v1AuthorsGetLambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::Lambda::Function&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;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;TracingConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Active&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorsLogicalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logicalIdFromResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authorsPath&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;v1AuthorsGetLambdaLogicalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logicalIdFromResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v1AuthorsGetLambda&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::ApiGateway::Method&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;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ResourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authorsLogicalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;restApiLogicalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Integration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Join&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&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;arn:&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;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::Partition&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;:apigateway:&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;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::Region&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;:lambda:path/2015-03-31/functions/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="nx"&gt;v1AuthorsGetLambdaLogicalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Arn&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invocations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some changes in the example code compared to the permalink but the idea is the same.&lt;/p&gt;

&lt;p&gt;While the functions expose to use might not be the most comprehensive compared to what we might want to do, I was at least able to figure out some way of testing what I wanted to. I hope that my thoughts and examples have helped someone along their way. These examples are what the CDK authors intended to the best of my knowledge, but if I learn something different later, I will either update this post or make a follow-up post. For now, happy coding!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>aws</category>
      <category>testing</category>
    </item>
    <item>
      <title>How To Use The DynamoDB Document Client</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Thu, 20 Jan 2022 09:00:09 +0000</pubDate>
      <link>https://dev.to/thomasstep/how-to-use-the-dynamodb-document-client-3h6e</link>
      <guid>https://dev.to/thomasstep/how-to-use-the-dynamodb-document-client-3h6e</guid>
      <description>&lt;p&gt;In my &lt;a href="https://thomasstep.com/blog/gtbwsa-chapter-9-dynamodb"&gt;Guide To Building With Serverless AWS&lt;/a&gt; chapter about DynamoDB, I mention using the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_dynamodb.html"&gt;Document Client&lt;/a&gt; to communicate with DynamoDB instead of some other alternatives like the lower-level client. In this post I would like to discuss and give code examples for some of the specific &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.API.html#HowItWorks.API.DataPlane"&gt;DynamoDB APIs&lt;/a&gt;. When I started using the Document Client I had a little bit of trouble translating the lower level client and APIs calls into Document Client calls but after seeing some examples and reading how I translate the payloads, I hope that all of that will be much easier for you. As a side note, what I will be showing in this post uses the AWS Javascript SDK &lt;strong&gt;v3&lt;/strong&gt;. While the lower versions (and potentially future versions) will most likely look similar, this code is specifically for version 3.&lt;/p&gt;

&lt;p&gt;Transforming a payload to include data types for DynamoDB calls can be tedious. For the sake of my own understanding, I have used the lower level SDK client to manually code the DynamoDB types, and having done that, I much prefer the Document Client for ease of use. The Document Client uses native Javascript types to conclude the data type for DynamoDB and then transforms the payload for us.&lt;/p&gt;

&lt;p&gt;The Document Client in the SDK is a thin layer on top of the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/index.html"&gt;lower-level/normal client&lt;/a&gt; that does the transformation. It is available in the &lt;code&gt;@aws-sdk/lib-dynamodb&lt;/code&gt; package but the &lt;code&gt;@aws-sdk/client-dynamodb&lt;/code&gt; is still required.&lt;/p&gt;

&lt;p&gt;Before I start showing the code examples I want to make note of a couple of assumptions that the code will be making.&lt;/p&gt;

&lt;p&gt;First is that the DynamoDB table is created and the name of the table is available through an environment variable called &lt;code&gt;TABLE_NAME&lt;/code&gt;. In the code examples, you will see &lt;code&gt;TableName: process.env.TABLE_NAME&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The code also assumes that the table is configured to use a partition key with the name &lt;code&gt;id&lt;/code&gt; and a sort key with the name &lt;code&gt;secondaryId&lt;/code&gt;. This will be seen in examples looking like the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Item: {
  id: 'myId',
  secondaryId: 'mySecondaryId',
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both the partition and sort key names can be configured to whatever you would like. The &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;secondaryId&lt;/code&gt; keys in the &lt;code&gt;Item&lt;/code&gt; object would then need to be changed accordingly. I suggest not hardcoding either of these values like I am in the examples. For the partition key (&lt;code&gt;id&lt;/code&gt;), I suggest using something like a generated UUID or a value unique to that item's data. The sort key (&lt;code&gt;secondaryId&lt;/code&gt;) value will be more dependent on your data model, but some suggestions could be using a configured value pulled from a module, an environment variable, or a value specific to that item's data.&lt;/p&gt;

&lt;p&gt;If you do not know much about data modeling, I highly suggest learning more and determining how to model your data before using DynamoDB. I lightly discuss modeling in &lt;a href="https://thomasstep.com/blog/gtbwsa-chapter-9-dynamodb"&gt;this post&lt;/a&gt; but there are other great resources out there from Rick Houlihan (find his re:Invent talks on YouTube) and &lt;a href="https://www.alexdebrie.com/"&gt;Alex DeBrie&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Remember that these are simply examples and you can change your code however you would like. I simply want to show what the calls could look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating the Document Client&lt;/li&gt;
&lt;li&gt;PutItem&lt;/li&gt;
&lt;li&gt;BatchWriteItem&lt;/li&gt;
&lt;li&gt;GetItem&lt;/li&gt;
&lt;li&gt;Query&lt;/li&gt;
&lt;li&gt;UpdateItem&lt;/li&gt;
&lt;li&gt;DeleteItem&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Creating the Document Client
&lt;/h2&gt;

&lt;p&gt;Creating the Document Client first involves creating a normal client and then initializing the Document Client with the normal client. I like to use a single module and then export the client for use in subsequent modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-dynamodb&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;DynamoDBDocument&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/lib-dynamodb&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;client&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;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&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;documentClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocument&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;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;PutItem&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a new item in DynamoDB involves either a &lt;code&gt;PutItem&lt;/code&gt; or &lt;code&gt;BatchWriteItem&lt;/code&gt; API call. An &lt;code&gt;Item&lt;/code&gt; is passed in and the partition and sort key need to be present. Any additional values in the &lt;code&gt;Item&lt;/code&gt; will be written as attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;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;myUniqueId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secondaryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondaryId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;BatchWriteItem&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This API was slightly more confusing to me. The top level of the payload contains a &lt;code&gt;RequestItems&lt;/code&gt; key. The values for &lt;code&gt;RequestItems&lt;/code&gt; are key-value pairs where the keys are table names and the values are arrays of requests being sent to each table. The requests in the arrays are also key-value pairs where the keys are either &lt;code&gt;PutRequest&lt;/code&gt; or &lt;code&gt;DeleteRequest&lt;/code&gt; and the values are either &lt;code&gt;Item&lt;/code&gt; (similar to &lt;code&gt;PutItem&lt;/code&gt;) or &lt;code&gt;Key&lt;/code&gt; (similar to &lt;code&gt;DeleteItem&lt;/code&gt;), respectively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myItems&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;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mars&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saturn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;myItems&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;myItem&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;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;PutRequest&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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Keep in mind that each partition and sort key will need&lt;/span&gt;
        &lt;span class="c1"&gt;// to be unique for each item&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;myUniqueId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secondaryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondaryId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;myItem&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;// Similar payload for DeleteRequest&lt;/span&gt;
    &lt;span class="c1"&gt;// DeleteRequest: {&lt;/span&gt;
    &lt;span class="c1"&gt;//   Key: {&lt;/span&gt;
    &lt;span class="c1"&gt;//     id: 'myUniqueId',&lt;/span&gt;
    &lt;span class="c1"&gt;//     secondaryId: 'secondaryId',&lt;/span&gt;
    &lt;span class="c1"&gt;//   },&lt;/span&gt;
    &lt;span class="c1"&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;batchWritePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;RequestItems&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;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;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;requests&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;await&lt;/span&gt; &lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;batchWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchWritePayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;GetItem&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Retrieving an item is fairly simple. We pass in the partition and sort key and get back a response containing some metadata and the &lt;code&gt;Item&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;documentClient&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="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TABLE_NAME&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="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;myUniqueId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secondaryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondaryId&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&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;Item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;Query&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are some additional fields in &lt;code&gt;query&lt;/code&gt; that might seem confusing at first. &lt;code&gt;KeyConditionExpression&lt;/code&gt; for a &lt;code&gt;query&lt;/code&gt; needs to define the value of the partition key and can optionally specify a comparison with a value for the sort key. In my example, the sort key is simply compared with equals (&lt;code&gt;=&lt;/code&gt;) to the value (&lt;code&gt;secondaryId&lt;/code&gt;) but I could have just as easily used any of the supported &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions"&gt;key condition expressions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next confusing parts are the &lt;code&gt;ExpressionAttributeNames&lt;/code&gt; and &lt;code&gt;ExpressionAttributeValues&lt;/code&gt;. Each is a way to dynamically reference a value in the &lt;code&gt;KeyConditionExpression&lt;/code&gt; without needing to do string substitution. With the Document Client, this is straightforward, but for some context, without the Document Client, we would have needed to add data types to the values in &lt;code&gt;ExpressionAttributeValues&lt;/code&gt;. The strings to be referenced by &lt;code&gt;ExpressionAttributeNames&lt;/code&gt; should be prefixed with a hash sign (&lt;code&gt;#&lt;/code&gt;) and &lt;code&gt;ExpressionAttributeValues&lt;/code&gt; should be prefixed with a colon (&lt;code&gt;:&lt;/code&gt;). The prefix characters are not a convention, they are explicitly stated in the documentation.&lt;/p&gt;

&lt;p&gt;After results have been gathered from the table, we can further refine the results by making DynamoDB filter what has been queried using the &lt;code&gt;FilterExpression&lt;/code&gt;. This is a good place to go over results with a fine-toothed comb, but just know that a &lt;code&gt;query&lt;/code&gt; consumes read requests units based on the results returned by the &lt;code&gt;KeyConditionExpression&lt;/code&gt;, not only the results returned after further refinement using the &lt;code&gt;FilterExpression&lt;/code&gt;. Again, this is a data modeling problem. The &lt;code&gt;FilterExpression&lt;/code&gt; uses the same syntax as the &lt;code&gt;KeyConditionExpression&lt;/code&gt; but can be written for any attribute in the &lt;code&gt;Item&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, the &lt;code&gt;ProjectionExpression&lt;/code&gt; is a comma-separated list of attributes that should be retrieved by DynamoDB and returned as a result of the &lt;code&gt;query&lt;/code&gt;. If we were querying the items from the &lt;code&gt;BatchWriteItem&lt;/code&gt;(#batchwriteitem) example, we would on retrieve &lt;code&gt;hello&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; but not &lt;code&gt;rings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We get back a response containing some metadata and the &lt;code&gt;Items&lt;/code&gt; we match the &lt;code&gt;KeyConditionExpression&lt;/code&gt; and &lt;code&gt;FilterExpression&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id = :partitionKey AND secondaryId = :sortKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#valueName&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;value&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;ExpressionAttributeValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:partitionKey&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;myUniqueId&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;:sortKey&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;secondaryId&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;:minValue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;FilterExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#valueName &amp;gt;= :minValue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ProjectionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello, value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&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;Items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;UpdateItem&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Updating an item is fairly simple. We pass in the partition and sort key as the &lt;code&gt;Key&lt;/code&gt;'s value and any updates to attributes as key-value pairs at the top level of the payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&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;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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;TABLE_NAME&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="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;myUniqueId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secondaryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondaryId&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="nx"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;DeleteItem&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html"&gt;AWS Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deleting an item in DynamoDB involves either a &lt;code&gt;DeleteItem&lt;/code&gt; or &lt;code&gt;BatchWriteItem&lt;/code&gt; API call. Deleting is as simple as retrieving, we pass in the item's partition and sort key and DynamoDB handles the rest.&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;await&lt;/span&gt; &lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TABLE_NAME&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="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;myUniqueId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secondaryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondaryId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>API Gateway Models</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Wed, 19 Jan 2022 09:00:09 +0000</pubDate>
      <link>https://dev.to/thomasstep/api-gateway-models-bhm</link>
      <guid>https://dev.to/thomasstep/api-gateway-models-bhm</guid>
      <description>&lt;p&gt;As almost anyone in software knows, iteration is key. Kaizen has proved itself over and over again, and I like to apply the same concept to my projects. My earlier projects started fairly rudimentary and simple but have grown more complex with larger scopes over time. One of the areas in particular that I have set out to consciously improve is my infrastructure. For some reason, AWS and infrastructure have continued to be fun and of interest to me over the years.&lt;/p&gt;

&lt;p&gt;I have written many APIs and configured many API Gateways to go with them. What once started as CloudFormation templates evolved into CDK code and my &lt;a href="https://www.npmjs.com/package/crow-api"&gt;CDK Construct&lt;/a&gt; which makes iteration even easier. The latest improvement that I have taken to is using API Gateway models to define my endpoints' schemas. Using models is something that I have wanted to work on for a while now, but I had not taken the time to properly learn what they are or how they can be configured up until now.&lt;/p&gt;

&lt;p&gt;Models are a way to define the accepted body using JSON and a syntax called &lt;a href="https://json-schema.org/"&gt;JSON schema&lt;/a&gt;. That is about as complicated as they are. Once you have a schema in mind, the syntax is also fairly straightforward. I will show an example of a JSON schema that I wrote for a CDK application and then explain the different pieces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  schema: apigateway.JsonSchemaVersion.DRAFT4,
  title: 'mySchema',
  type: apigateway.JsonSchemaType.OBJECT,
  required: ['startTime', 'endTime'],
  properties: {
    startTime: {
      type: apigateway.JsonSchemaType.STRING,
      format: 'date-time',
    },
    endTime: {
      type: apigateway.JsonSchemaType.STRING,
      format: 'date-time',
    },
    title: { type: apigateway.JsonSchemaType.STRING },
  },
  additionalProperties: false,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start, we need to define the JSON Schema version we are using. At the time of my writing this, draft 4 is the current most up-to-date version. Then comes the title. The other keys are starting to define the schema. Since my endpoint was a POST that accepted a JSON object, I needed to define my root property type as &lt;code&gt;apigateway.JsonSchemaType.OBJECT&lt;/code&gt;. Since my root type is an object, I can then use the &lt;code&gt;properties&lt;/code&gt; key to define what the object's body looks like. The "type" for each property in my parent object is also a JSON Schema, so this is a sort of recursive syntax.&lt;/p&gt;

&lt;p&gt;My schema accepts three and only three potential properties: &lt;code&gt;startTime&lt;/code&gt;, &lt;code&gt;endTime&lt;/code&gt;, and &lt;code&gt;title&lt;/code&gt;. The &lt;code&gt;additionalProperties: false&lt;/code&gt; piece tells API Gateway to return a 400 error if the caller supplies keys outside of what I have defined. The &lt;code&gt;required: ['startTime', 'endTime']&lt;/code&gt; piece also tells API Gateway to return a 400 error if the caller does not supply either of those required properties. For the time being, the &lt;code&gt;additionalProperities&lt;/code&gt; and &lt;code&gt;required&lt;/code&gt; properties are the only behavioral properties I needed.&lt;/p&gt;

&lt;p&gt;Once the root object's properties are configured, we can move on to each of the children. The type and format for &lt;code&gt;startTime&lt;/code&gt; and &lt;code&gt;endTime&lt;/code&gt; are defined so that a small amount of pre-validation can occur. This eases the burden on my code for light validation so I can focus solely on deeper validation. This brings me to the main reason I wanted to start using models: API Gateway validates payloads for me and rejects those that do not conform without me spending a cent. In a similar fashion to authorizers, AWS will front the bill of validating that a call is "up to snuff" before spinning up a Lambda and charging me for it.&lt;/p&gt;

&lt;p&gt;Once we have a JSON Schema written up, configuring it becomes a matter of applying the JSON to the correct property on the API Gateway Method and configuring a Request Validator to validate the body. I wish I would have started using models earlier because they save my time writing code to validate payloads before executing business logic on them. I plan on going back to some of my older projects and APIs to refactor in using models. I hope this convinced you to start using models, and if you have any further questions, including implementation-specific questions, feel free to reach out to me.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS CDK Example For API Gateway And SNS Integration</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Mon, 17 Jan 2022 09:00:09 +0000</pubDate>
      <link>https://dev.to/thomasstep/aws-cdk-example-for-api-gateway-and-sns-integration-5dee</link>
      <guid>https://dev.to/thomasstep/aws-cdk-example-for-api-gateway-and-sns-integration-5dee</guid>
      <description>&lt;p&gt;TL;DR: The CDK code I used to &lt;a href="https://github.com/thomasstep/aws-cdk-reference/blob/b1ba815bbbe262530673a01c01e9bb5873223461/lib/api-gateway-sns-integration.ts"&gt;directly integrate API Gateway and SNS is here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While working on one of my more recent projects, I came across the need to create a completely asynchronous API path in API Gateway. I have &lt;a href="https://dev.to/blog/writing-asynchronous-lambda-functions-with-node"&gt;used SNS&lt;/a&gt; in the past for asynchronous communication but in prior scenarios, I published messages to SNS using the SDK in a Lambda function. This time around there was no execution needed before calling SNS which made a good use case for an API Gateway integration directly with SNS.&lt;/p&gt;

&lt;p&gt;Integrating API Gateway with other AWS services is possible, but I have read articles online claiming that the integration is not as easy as it could or should be. It seemed as if there were difficulties because the API Gateway integration more or less needs to be hardcoded with the proper API calls to the AWS service it is being integrated with. As someone who has configured this type of integration, I can confidently say that those articles were correct. I repeatedly needed to reference AWS documentation. The trickiest parts were needing a separate IAM Role (something I have not had to worry about since moving to the AWS CDK), the &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; properties, getting the &lt;code&gt;requestParameters&lt;/code&gt; and &lt;code&gt;requestTemplates&lt;/code&gt; to match up and work, and making the &lt;code&gt;requestTemplates&lt;/code&gt; with &lt;a href="https://velocity.apache.org/"&gt;Velocity&lt;/a&gt; work correctly because I wanted to pass in JSON.&lt;/p&gt;

&lt;p&gt;The AWS CDK takes care of most IAM provisioning needed for resources to correctly operate. If actions are not configured out of the box, there are normally easy calls to make to get resources set up to complete their tasks. For some reason, API Gateway integrations do not come with that support out of the box. This problem was trivial to fix though since it only involved creating a Role resource and granting it the correct permissions. Either way, I think that this could be one area that the CDK could improve upon in the future.&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;apiGatewayRole&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;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;integration-role&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;assumedBy&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;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apigateway.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grantPublish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiGatewayRole&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring the correct &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; prop was the next big hurdle that I had to cross. According to the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.AwsIntegrationProps.html"&gt;CDK docs for AwsIntegrationProps&lt;/a&gt;, the &lt;code&gt;action&lt;/code&gt; is "the AWS action to perform in the integration" and the &lt;code&gt;path&lt;/code&gt; is "the path to use for path-base APIs." None of that information helped me out nor did it tell me that one of the two props is necessary for correct integration. I started with &lt;code&gt;action: 'Publish&lt;/code&gt;. Nothing. So I started looking around the internet for answers. If it had not been for Alex DeBrie and his post about &lt;a href="https://www.alexdebrie.com/posts/aws-api-gateway-service-proxy/"&gt;API Gateway service proxies&lt;/a&gt; I probably would have taken a week to figure this out through trial and error. If you read through that post, you will notice that there is no mention of &lt;code&gt;path&lt;/code&gt; or &lt;code&gt;action&lt;/code&gt; used as a configuration item, but he does include &lt;code&gt;Action=Publish&lt;/code&gt; in the request template and the integration's URI includes &lt;code&gt;sns:path//&lt;/code&gt;. From those two bits, I pieced together &lt;a href="https://github.com/thomasstep/aws-cdk-reference/blob/b1ba815bbbe262530673a01c01e9bb5873223461/lib/api-gateway-sns-integration.ts#L45-L56"&gt;something that works&lt;/a&gt;. Keep in mind that I am not sure what I did is correct, all I know is that it works. When API Gateway constructs the URL for SNS the double slash (&lt;code&gt;//&lt;/code&gt;) is in the path, which looks ugly and is probably an indication that something is not optimally configured. For now, this will work, but I plan on revisiting it in the future.&lt;/p&gt;

&lt;p&gt;Next on the list of hurdles was correctly configuring the &lt;code&gt;requestParameters&lt;/code&gt; and &lt;code&gt;requestTemplates&lt;/code&gt;. However, this was less a lack of documentation and more so a lack of me reading said documentation. The &lt;code&gt;requestParameters&lt;/code&gt; are input to the integration, and in my case, I needed to set the &lt;code&gt;Content-Type&lt;/code&gt; header to &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;. For some reason, it did not click with me until too late what &lt;code&gt;requestParameters&lt;/code&gt; were for and that they should not be the same as the &lt;code&gt;application/json&lt;/code&gt; that I used as the key in &lt;code&gt;requestTemplates&lt;/code&gt;. Figuring out &lt;code&gt;requestTemplates&lt;/code&gt; was easier for me. It is a mapping between a &lt;code&gt;Content-Type&lt;/code&gt; header and a template used to transform the request into a payload for the backend. I think what tripped me up is that I thought the value used in &lt;code&gt;requestParameters&lt;/code&gt; needed to be the same as the key used in &lt;code&gt;requestTemplates&lt;/code&gt;, which is not the case.&lt;/p&gt;

&lt;p&gt;Even though I said that figuring out the &lt;code&gt;requestTemplates&lt;/code&gt; was slightly easier for me, figuring out the Velocity template syntax was difficult. I knew that I should be able to pass in JSON to SNS's Publish action, but since I had previously only used SNS from the SDK, I did not know how to translate what the SDK did into Velocity. My API had IDs in its path that I wanted to include in the payload to SNS. The biggest realization for myself was that I needed to manually write out and escape the JSON where I had previously thought that Velocity would do that for me. After I figured out that I needed to hardcode the JSON and inject the parameters where they were the values, I was off to the races.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Message=$util.urlEncode('{"someParam":"')$input.params('paramName')$util.urlEncode('"}')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite all the trouble that integrating API Gateway directly with SNS gave me, I am glad I went through it. This is a valuable integration and configuration to have set up. I have already implemented it more than once. I am sure that I will use it more in the future and iterate on top of what I already know to make the configuration run even smoother.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>typescript</category>
      <category>devops</category>
      <category>serverless</category>
    </item>
    <item>
      <title>I Wrote a Guide to Serverless AWS</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Fri, 17 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/thomasstep/i-wrote-a-guide-to-serverless-aws-hpj</link>
      <guid>https://dev.to/thomasstep/i-wrote-a-guide-to-serverless-aws-hpj</guid>
      <description>&lt;p&gt;EDIT: &lt;a href="https://dev.to/blog/gtbwsa-chapter-1-introduction"&gt;The guide has been released!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To newcomers, AWS might seem like a big confusing jumble of buzzwords and weird service names. When I started my journey into learning AWS and cloud engineering, I thought the same thing. I started my journey as a software engineer with some networking and traditional operations experience, but the cloud and its constituents were still very much foreign to me. Fast forward two years and I have learned more than I ever thought I would about AWS. I have worked with and weeded through a lot of the AWS services, and I have somewhat strong opinions about how to build workloads and which services are worth my time.&lt;/p&gt;

&lt;p&gt;My personal preference (as is probably apparent by my previous AWS blog posts) is to use serverless technologies because they are quick to develop on and the pricing model suits new applications and startups well. The guide that I wrote is focused on the core serverless technologies that I have personally used to create applications. My goal throughout writing this guide was to provide a framework of AWS services to focus on for newcomers, the fundamental concepts of those services, and the integrations between those services that can create fully-fledged, production-ready serverless applications. This is what I wish existed when I first started learning AWS.&lt;/p&gt;

&lt;p&gt;The last month and a half of my free time have been dedicated to writing down and organizing this information, and I feel much better getting it out of my head and into the world. I will host a free version on my website with each chapter being a new blog post, and I will offer a PDF version with a bit of additional content through GumRoad. The blog version will be out first with the full eBook version out soon thereafter. Once it is out feel free to send me feedback about what you did and did not like or if there are any areas you would have liked me to touch on more. I look forward to hearing from you!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>beginners</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Destructuring Objects in Javascript</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Thu, 12 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/thomasstep/destructuring-objects-in-javascript-5b2o</link>
      <guid>https://dev.to/thomasstep/destructuring-objects-in-javascript-5b2o</guid>
      <description>&lt;p&gt;I first learned about destructuring objects a few months into learning about Javascript. I might have been late to the party but I am glad I joined. Destructuring is one of my favorite features of the language because it looks and feels so clean and succinct. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment"&gt;official definition&lt;/a&gt; is unpacking values from array or properties from objects into distinct variables. Today, I only want to go over a few examples with objects, but maybe I will make another post with examples for destructuring arrays.&lt;/p&gt;

&lt;p&gt;The first and easiest to understand example is simple value extraction. Given the key names of a certain object, we can pull out the values of those keys for operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initialObject = {
  hello: 'world',
  other: 'iamavalue',
  numberValue: 42,
};

const {
  hello,
  other,
  numberValue,
} = initialObject;

console.log(hello); // 'world'
console.log(other); // 'iamavalue'
console.log(numberValue); // 42

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

&lt;/div&gt;



&lt;p&gt;Seems simple enough. The syntax also allows us to give those new &lt;code&gt;const&lt;/code&gt;s different names instead of the default names corresponding to the keys. Providing new names might look a bit weird at first but starts to become second nature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initialObject = {
  hello: 'world',
  other: 'iamavalue',
  numberValue: 42,
};

const {
  hello: planet,
  other: whatAmI,
  numberValue: randomNumber,
} = initialObject;

console.log(planet); // 'world'
console.log(whatAmI); // 'iamavalue'
console.log(randomNumber); // 42

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

&lt;/div&gt;



&lt;p&gt;The key’s name is on the left of the colon (&lt;code&gt;:&lt;/code&gt;) and the new variable’s name is on the right. Destructuring this way sometimes makes more sense if the key names are generic or if multiple objects with the same keys are being destructured since the &lt;code&gt;const&lt;/code&gt; variable names would otherwise conflict. What if our assumptions about the object’s contents are false? At the top level, the value is returned as &lt;code&gt;undefined&lt;/code&gt;, but there is a way to provide default values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let conditionalObject = {};

if (Math.random() &amp;gt; 0.5) {
  conditionalObject = {
    hello: 'world',
    other: 'iamavalue',
    numberValue: 42,
  };
}

const {
  hello,
  other = 'defaultOtherString',
  numberValue: randomNumber = 3,
} = conditionalObject;

console.log(hello); // 'world' || undefined
console.log(other); // 'iamavalue' || 'defaultOtherString'
console.log(randomNumber); // 42 || 3

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

&lt;/div&gt;



&lt;p&gt;Let’s say you have an object that is conditionally populated with properties; attempting to destructure those properties could be a hit or a miss. The example above shows what happens if we do not provide a default value (variable is &lt;code&gt;undefined&lt;/code&gt;), how to provide a default value (&lt;code&gt;other = 'defaultOtherString'&lt;/code&gt;), and how to provide a default value to a renamed property (&lt;code&gt;numberValue: randomNumber = 3&lt;/code&gt;). The syntax for providing a default value looks the same as providing a default value to a function, so we add an equals sign (&lt;code&gt;=&lt;/code&gt;) and the default value to the right side. The syntax for providing a default value to a renamed variable name needs to follow a certain order, but the order is intuitive. First, we provide a new variable name with the colon (&lt;code&gt;:&lt;/code&gt;) syntax like we saw before, then we tack on the default value with its syntax.&lt;/p&gt;

&lt;p&gt;What if we want to overwrite the value of an existing variable with a value retrieved while destructuring? That is also possible by assigning without declaring. The syntax does look a little different, but it is an alternative method of destructuring that can be put to good use. I have used something similar to this in the past to override existing variables that were previously defined as a way of using default values. This also works well for destructuring in a condition but declaring the variable with &lt;code&gt;let&lt;/code&gt; outside of the condition’s block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let hello;
let whatAmI;
let randomNumber;

const initialObject = {
  hello: 'world',
  other: 'iamavalue',
};

({
  hello,
  other: whatAmI,
  numberValue: randomNumber = 88,
} = initialObject);

console.log(hello); // 'world'
console.log(whatAmI); // 'iamavalue'
console.log(randomNumber); // 88

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

&lt;/div&gt;



&lt;p&gt;Putting parentheses around the destructuring statement executes this code as a statement instead of seeing the &lt;code&gt;{ ... }&lt;/code&gt; on the left of the equals (&lt;code&gt;=&lt;/code&gt;) as a block. It is also worth noting that a semicolon (&lt;code&gt;;&lt;/code&gt;) needs to be placed after the preceding statement or the destructuring will be viewed as trying to call a function. The previous syntax additions that I have examples for are also available: renaming variables and providing default values.&lt;/p&gt;

&lt;p&gt;Destructuring also works without statically defining the key names we want to pull out. Using similar square bracket (&lt;code&gt;[...]&lt;/code&gt;) notation as we would for accessing an object’s values, we can destructure using dynamic values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initialObject = {
  hello: 'world',
  other: 'iamavalue',
  numberValue: 42,
};

let firstProperty = 'hello';
let secondProperty = 'other';

if (Math.random() &amp;gt; 0.5) {
  firstProperty = 'numberValue';
  secondProperty = 'hello';
}

const {

  [secondProperty]: second = 'defaultValue',
} = initialObject;

console.log(first);
console.log(second);
/**
 * Either 'world' and 'iamavalue'
 * or 42 and 'world'
 */

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

&lt;/div&gt;



&lt;p&gt;Destructuring this way requires us to provide new variables names and optionally provide default values (in this example the default behavior would not be encountered, I simply wanted to show that it is an option). &lt;strong&gt;&lt;em&gt;CHECK THAT THIS IS TRUE&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So far I have only shown examples with a flat object, but what about destructuring more complex nested objects?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initialObject = {
  hello: 'world',
  other: 'iamavalue',
  numberValue: 42,
  nestedObject: {
    nestedKey: 'nestedValue',
    ECHO: {
      ECHo: {
        ECho: {
          Echo: 'echo',
        },
      },
    },
    unusedNestedKey: 901,
  },
};

const {
  hello,
  nestedObject: {
    nestedKey,
    ECHO: {
      ECHo: partialEchoObject
    }
  },
} = initialObject;

console.log(hello); // 'world'
console.log(nestedKey); // 'nestedValue'
console.log(partialEchoObject); // ECho: { Echo: 'echo' }

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

&lt;/div&gt;



&lt;p&gt;As we can see, it is fairly straightforward to destructure nested objects. One thing to note is that we do not have to destructure until we hit the end of the nested objects, which I demonstrated with the nested echoing objects.&lt;/p&gt;

&lt;p&gt;One final use for this syntax that I wanted to highlight is copying an object but “removing” certain keys from the copy. Instead of using &lt;code&gt;delete&lt;/code&gt;, we can use a combination of destructuring and spreading to pull out those unwanted keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initialObject = {
  hello: 'world',
  other: 'iamavalue',
  numberValue: 42,
};

const {
  hello,
  ...rest
} = initialObject;

console.log(hello); // 'world'
console.log(rest); // { other: 'iamavalue', numberValue: 42 }

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

&lt;/div&gt;



&lt;p&gt;The value placed in the &lt;code&gt;rest&lt;/code&gt; variable, which is spread, is the &lt;code&gt;initialObject&lt;/code&gt; minus the properties that have already been used in the destructuring (&lt;code&gt;hello&lt;/code&gt; in this example). I love finding excuses to use this nifty pattern. Note that &lt;code&gt;...rest&lt;/code&gt; does not have a comma (&lt;code&gt;,&lt;/code&gt;) after it, this is done on purpose.&lt;/p&gt;

&lt;p&gt;These are all examples that I have personally used to destructure objects in Javascript. Other patterns exist that I have not used, but I believe these are some of the more common ones. Arrays can also be destructured, but that is a topic for another day. Hopefully, this gave you some context and new tools to add to your kit.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Writing Asynchronous Lambda Function with Node</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Tue, 10 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/thomasstep/writing-asynchronous-lambda-function-with-node-21c6</link>
      <guid>https://dev.to/thomasstep/writing-asynchronous-lambda-function-with-node-21c6</guid>
      <description>&lt;p&gt;When I first started diving into cloud engineering and learning about microservices, I kept hearing about “event-driven architecture”. I understood how to write asynchronous code in Node.js, and I understood how to write code for Lambda functions. But I caught myself awaiting async code before I returned from a Lambda handler without using the result of that awaited function. Something felt off, but I did not know what else to do.&lt;/p&gt;

&lt;p&gt;The way that async Lambda handlers work (at least with Node.js) is by running code and then “finishing execution” whenever the handler returns. The Lambda might finish execution, but if an async function was still running in the background (waiting on an API call for example) then that asynchronous execution may or may not finish before the Lambda itself shuts down. There are loads of explanations and documentation out there about the Node.js event loop and how it works with AWS Lambda. That documentation has better explanations of what is happening than I am prepared to offer at this time. The quick and dirty solution that most people use (myself included for a long time) is to simply await all async functions before returning from the Lambda handler. However, there is a better way to handle asynchronous execution. After all, we are talking about the cloud and Amazon, and surely they have run into this problem before.&lt;/p&gt;

&lt;p&gt;My experience with async Node.js code and Lambda was limited to the above solution until I started working on a side project. I wanted to focus on speed and lower latency, so I naturally needed to learn more about writing asynchronous systems and code. After reading and experimenting I found a solution that was staring me right in the face this entire time: AWS SNS. For some reason, I had never completely connected the dots before, but SNS allowed me to call an async function without awaiting the result to make sure that execution finished. I had interacted with SNS before, but I failed to think of it as a convenient way to run code in the background without slowing down a Lambda function.&lt;/p&gt;

&lt;p&gt;One thing that I knew but never completely put into practice was the fact that I was writing Lambda &lt;em&gt;functions&lt;/em&gt;. Instead of writing an async function and running it in my critical path’s Lambda, I could take that same async function, deploy it as its own Lambda &lt;em&gt;function&lt;/em&gt;, create an SNS topic, make that Lambda a consumer of the SNS topic, and call my function asynchronously by publishing to SNS from my critical path.&lt;/p&gt;

&lt;p&gt;Calling the SNS API in my main Lambda is faster than calling the async function and awaiting it, so my main Lambda is allowed to return to the user with minimum latency. My async function is then triggered by SNS to run without needing to worry about how long it takes. It’s a win-win.&lt;/p&gt;

&lt;p&gt;Lambda is great. SNS is great. For whatever reason, it took me a while to realize the power that combining Lambda and SNS provides. Learn from my mistake and enjoy this combination made in heaven. Use it. Love it. Create asynchronous, event-driven systems.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Centering a Div With Tailwind CSS</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Fri, 23 Jul 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/thomasstep/centering-a-div-with-tailwind-css-36kn</link>
      <guid>https://dev.to/thomasstep/centering-a-div-with-tailwind-css-36kn</guid>
      <description>&lt;p&gt;Somehow centering a &lt;code&gt;div&lt;/code&gt; still seems to trouble people. I have mostly been working with Tailwind CSS lately and I wanted to quickly share how I center a div with Tailwind. I’ll first show the examples and then describe what is happening.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="flex flex-row min-h-screen justify-center items-center"&amp;gt;
  I am centered
&amp;lt;/div&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;The same classes work with either &lt;code&gt;flex-row&lt;/code&gt; or &lt;code&gt;flex-col&lt;/code&gt;, which set the flexbox’s main axis either horizontally or vertically, respectively. Setting the height with &lt;code&gt;min-h-screen&lt;/code&gt; is just an easy way to take up an entire screen’s view. The final two classes are where I needed to learn a tiny amount of CSS.&lt;/p&gt;

&lt;p&gt;I played around with justification and alignment far too long before I finally looked into what they affect. &lt;code&gt;justify-content&lt;/code&gt; refers to how the content should be positioned along the main axis, while &lt;code&gt;align-content&lt;/code&gt; refers to how the content should be positioned along the cross axis. &lt;a href="https://tailwindcss.com/docs/justify-content"&gt;Justifying content&lt;/a&gt; and &lt;a href="https://tailwindcss.com/docs/align-content"&gt;aligning content&lt;/a&gt; with Tailwind are simple one-to-one mappings between their classes and the actual CSS, so once I understood how the CSS worked, I understood how the utility worked.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>html</category>
    </item>
    <item>
      <title>A Guide To Using JWT In Javascript</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Wed, 21 Jul 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/thomasstep/a-guide-to-using-jwt-in-javascript-2bd2</link>
      <guid>https://dev.to/thomasstep/a-guide-to-using-jwt-in-javascript-2bd2</guid>
      <description>&lt;p&gt;JWTs are a popular form of authenticating users for web applications. I won’t get into the debate about whether or not we should be using JWTs for this purpose because that is a different discussion entirely. There are use cases that are better suited for using JWTs than others, and I am going to expect that you have landed here after already determining that JWTs are a good fit for what you hope to accomplish.&lt;/p&gt;

&lt;p&gt;JWTs are a convenient way for us to store a small bit of public information generated by a server acting as the source of truth to be distributed to potentially anything. The server that generates and signs the JWT has access to a private key while the public key will need to be distributed to servers consuming the JWTs. By using asymmetrical keys we can ensure that everyone can read the information contained in the JWT while making sure that only the authentication server can sign those JWTs.&lt;/p&gt;

&lt;p&gt;For simplicity, I will assume that the intent of signing and verifying JWTs is for authentication purposes. While JWTs can be used for other reasons, I imagine authentication is the widest used case and what anyone who found this article is planning on doing. I am also going to assume that you have none of this set up yet. I will walk you through the following: creating public and private keys, creating a JWKS, signing a JWT, and finally verifying a JWT.&lt;/p&gt;

&lt;p&gt;One prerequisite for all of this is the npm package &lt;code&gt;jose&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Public and Private Keys
&lt;/h2&gt;

&lt;p&gt;I have seen a small amount of debate out on the interwebs about what algorithms should be used for creating your keys. From my point of view (and as of this writing) the best way to do this is using RSA. The &lt;a href="https://datatracker.ietf.org/doc/html/rfc7518#section-3.1"&gt;JWA &lt;code&gt;alg&lt;/code&gt;&lt;/a&gt; for this is &lt;code&gt;RS256&lt;/code&gt;. There are two ways of generating these key pairs: using the command line and using &lt;code&gt;jose&lt;/code&gt;. My personal preference is to use &lt;code&gt;jose&lt;/code&gt;, but the command line tools are just as effective.&lt;/p&gt;

&lt;p&gt;To generate the key pairs using the command line run the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -m PEM -f jwt.key -N ""
openssl rsa -in jwt.key -pubout -outform PEM -out jwt.key.pub
cat jwt.key
cat jwt.key.pub

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

&lt;/div&gt;



&lt;p&gt;To generate the key pairs using &lt;code&gt;jose&lt;/code&gt; run the following script or some variation of them. It would be beneficial to store the produced strings in files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { generateKeyPair } = require('jose/util/generate_key_pair');

(async () =&amp;gt; {
  const { publicKey, privateKey } = await generateKeyPair('RS256');
  // https://nodejs.org/api/crypto.html#crypto_class_keyobject
  const publicKeyString = publicKey.export({
    type: 'pkcs1',
    format: 'pem',
  });
  const privateKeyString = privateKey.export({
    type: 'pkcs1',
    format: 'pem',
  });
  console.log(publicKeyString);
  console.log(privateKeyString);
})();

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

&lt;/div&gt;



&lt;p&gt;Both of these methods provide the same outcome. They generate RSA asymmetric keys. These are what allow us to properly sign a JWT.&lt;/p&gt;

&lt;p&gt;The private key must not be given out to anything other than your authentication server. The private key is used to sign the JWTs and the JWT consumers use the public key to verify that the JWT came from our auth server, so if anyone else gains access to it, then they can pretend that they are our authentication server. This is obviously a no-no.&lt;/p&gt;

&lt;p&gt;The way this all works is that the private key is used to encrypt the JWT. The public key can then be used to unencrypt the JWT. A public key can only unencrypt what its corresponding private key encrypted. No other public key can unencrypt the JWT. Conversely, no other private key can encrypt the same way that our new private key can. This means that whenever our new public key is used to successfully verify a JWT, we gain the knowledge that our authentication server approved the JWT’s authenticity from the start.&lt;/p&gt;

&lt;p&gt;This is the premise of asymmetric encryption. While it’s not necessary to know anything more than what I outlined, it would not hurt to obtain a deeper understanding of this concept. Asymmetric encryption is a deep rabbit hole to go down though so learn at your own pace.&lt;/p&gt;

&lt;p&gt;Back to the task at hand. As you might have guessed by now, the public key should be given out. This allows anyone to verify that the JWT given to them did in fact come from our authentication server. That’s a good thing. While it is valid to give out the public key in the form of a file (like what we just created) there is another type of “JSON Web Thing” that allows us to distribute the public key at scale: JWKS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a JWKS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7517"&gt;JWKS stands for JSON Web Key Set&lt;/a&gt; and they are a convenient way to distribute public keys. &lt;a href="https://crowauth.com/v1/jwks.json"&gt;Here is an example of a JWKS in the wild.&lt;/a&gt; The idea here is that JWKSs can be hosted as a simple JSON file to then be accessed by anything using a corresponding JWT. Building them off of public keys in &lt;code&gt;jose&lt;/code&gt; is just as easy as creating the initial key pairs.&lt;/p&gt;

&lt;p&gt;If the public key is in a file (if it was created using the command line), then the file will need to be read in, converted to the Node &lt;code&gt;crypto&lt;/code&gt; library’s representation of a key, and converted using &lt;code&gt;jose&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { fromKeyLike } = require('jose/jwk/from_key_like');

const pubKeyPath = path.resolve(__dirname, 'path/to/jwt.key.pub');
const pubKey = fs.readFileSync(pubKeyPath, 'utf8');
const cryptoPublicKey = crypto.createPublicKey(pubKey);
fromKeyLike(cryptoPublicKey).then((publicJwk) =&amp;gt; console.log(publicJwk));

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

&lt;/div&gt;



&lt;p&gt;We can also create these directly after generating the public key from &lt;code&gt;jose&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { generateKeyPair } = require('jose/util/generate_key_pair');
const { fromKeyLike } = require('jose/jwk/from_key_like');

(async () =&amp;gt; {
  const { publicKey, privateKey } = await generateKeyPair('RS256');
  // https://nodejs.org/api/crypto.html#crypto_class_keyobject
  const publicKeyString = publicKey.export({
    type: 'pkcs1',
    format: 'pem',
  });
  const privateKeyString = privateKey.export({
    type: 'pkcs1',
    format: 'pem',
  });
  console.log(publicKeyString);
  console.log(privateKeyString);

  const publicJwk = await fromKeyLike(publicKey);
  console.log(publicJwk);
})();

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

&lt;/div&gt;



&lt;p&gt;In both cases, the output should look similar to the following JSON Web Key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

  "kty": "RSA",
  "n": "wi5rLEy1U7m8rU8bQn2GeJ_g_XisJesbzI0N0QbYF3BNaEuQUwXOnh2ME8bOyKBrpXLik5AvljKp8HjwKG1x456kueJJoullYEBtrSRydnNaOQmUno1GQEcreCnRBZzodi9kw0YgsQvEfyxGwxI1NYSS8mdCSgT_BjOw5veHFfK-kdbJSa4mBkncKQCImArdAptKuvMciB3uSCfGqq1lZdBnsDR1O4isltDMBCLlAA9LQaXpksvQ99OROp965J0AFn9Vy64mwrhuonZ2c0C_dAAHJ_NSmmzI59xhB5QmCasINzGNNYBqxSzqRxCOpPcXt_D16il7nvsSIZoVuQDp3jsXO5fWONx5HtApO5zm7NpfSv800cFQjgQ8GHPqdVdufpKgAXaMxFlnLhgWP2QHTIpY2Mmy5zbKAEtTraWmYQs-cMhhj7sAzXNk6Wt25r9fyFVhmzGwhNmp4eUiVhiLKRTDuDSsFMaky__mtHcOEUZSvtyUEYQ2fqnHzlsBP4Ai8_Hr6d-qPQLmidR-69U5VQb6ftBGOeivzClSVRDfbKW7jtez2zB39FPx6Wm_FZqR1vBMmSNt1mJH2laIxkIh2qrkMCgLmMlkspZX8r3_VTRfUJcTcMWkzX16O8XzJeuI9YGgupF4K7wmzEmj1qZWgyXCSrB6L3873W8kPEui7lc",
  "e": "AQAB"
}

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

&lt;/div&gt;



&lt;p&gt;We’re almost there. A JWKS is an array of JSON Web Keys that form the key set. It is possible to have multiple public keys in one JWKS that are identified by the &lt;code&gt;kid&lt;/code&gt; (Key ID) parameter. That being said, I have only ever seen single entry JWKSs. Putting our new JSON Web Key into a JWKS could look something like the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "keys": [
    {
      "use": "sig",
      "kty": "RSA",
      "n": "wi5rLEy1U7m8rU8bQn2GeJ_g_XisJesbzI0N0QbYF3BNaEuQUwXOnh2ME8bOyKBrpXLik5AvljKp8HjwKG1x456kueJJoullYEBtrSRydnNaOQmUno1GQEcreCnRBZzodi9kw0YgsQvEfyxGwxI1NYSS8mdCSgT_BjOw5veHFfK-kdbJSa4mBkncKQCImArdAptKuvMciB3uSCfGqq1lZdBnsDR1O4isltDMBCLlAA9LQaXpksvQ99OROp965J0AFn9Vy64mwrhuonZ2c0C_dAAHJ_NSmmzI59xhB5QmCasINzGNNYBqxSzqRxCOpPcXt_D16il7nvsSIZoVuQDp3jsXO5fWONx5HtApO5zm7NpfSv800cFQjgQ8GHPqdVdufpKgAXaMxFlnLhgWP2QHTIpY2Mmy5zbKAEtTraWmYQs-cMhhj7sAzXNk6Wt25r9fyFVhmzGwhNmp4eUiVhiLKRTDuDSsFMaky__mtHcOEUZSvtyUEYQ2fqnHzlsBP4Ai8_Hr6d-qPQLmidR-69U5VQb6ftBGOeivzClSVRDfbKW7jtez2zB39FPx6Wm_FZqR1vBMmSNt1mJH2laIxkIh2qrkMCgLmMlkspZX8r3_VTRfUJcTcMWkzX16O8XzJeuI9YGgupF4K7wmzEmj1qZWgyXCSrB6L3873W8kPEui7lc",
      "e": "AQAB",
      "kid": "3d911ijttg0k80u2k74ax0hxeuhnd9njad7oa6nf",
      "alg": "RS256",
      "key_ops": [
        "verify"
      ]
    }
  ]
}

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

&lt;/div&gt;



&lt;p&gt;There are a few tweaks that need to be made (either programmatically or manually). The first and most obvious is that a JWKS needs to have a top-level key named &lt;code&gt;keys&lt;/code&gt; with a value that is an array of JSON Web Keys. The added keys to the generated JSON Web Key are &lt;code&gt;use&lt;/code&gt;, &lt;code&gt;kid&lt;/code&gt;, &lt;code&gt;alg&lt;/code&gt;, and &lt;code&gt;key_ops&lt;/code&gt; which stand for Public Key Use, Key ID, Algorithm, and Key Operations, respectively.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;use&lt;/code&gt; can either be &lt;code&gt;sig&lt;/code&gt; (signature) or &lt;code&gt;enc&lt;/code&gt; (encryption). &lt;code&gt;sig&lt;/code&gt; indicates that the key is used for verifying the signature on data. &lt;code&gt;enc&lt;/code&gt; indicates that the key is used for encrypting data.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kid&lt;/code&gt; is a unique string that I generated used to identify JSON Web Keys within a JWKS.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;alg&lt;/code&gt; identifies the algorithm intended for use with the key. This is the same value as what was passed into &lt;code&gt;generateKeyPair&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;key_ops&lt;/code&gt; identifies the intended operations upon which the JSON Web Key can be used. Some values for this array are &lt;code&gt;sign&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt;, and &lt;code&gt;encrypt&lt;/code&gt; among others. &lt;a href="https://datatracker.ietf.org/doc/html/rfc7517#section-4.3"&gt;This is a link to the RFC defining the values for this key.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once all of this is constructed, we have our JWKS. I suggested writing this information to a file and hosting it at an endpoint. It would also be completely valid to write it as a BLOB to a database and return it on request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signing a JWT
&lt;/h2&gt;

&lt;p&gt;If we complete this piece correctly, then verifying our JWT will be a breeze. If something goes awry…well, let’s just hope that doesn’t happen. First, let’s go back to what we learned about asymmetric encryption in the first section. We use our private key to encrypt a payload and our public key (now in the form of a JWKS) to unencrypt the payload. To sign our JWT, this code will need access to the private key we created in the first section. In addition, we can set claims and create a payload for our JWT as &lt;a href="https://datatracker.ietf.org/doc/html/rfc7519"&gt;outlined in the RFC&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(async () =&amp;gt; {
  const { publicKey, privateKey } = await generateKeyPair('RS256');
  // https://nodejs.org/api/crypto.html#crypto_class_keyobject
  const publicKeyString = publicKey.export({
    type: 'pkcs1',
    format: 'pem',
  });
  const privateKeyString = privateKey.export({
    type: 'pkcs1',
    format: 'pem',
  });

  const token = await new SignJWT({
      myClaim: true,
    })
    .setProtectedHeader({
      typ: 'JWT',
      alg: 'RS256',
    })
    .setIssuer('https://example.com')
    .setSubject('uniqueUserId')
    .setAudience('myapp.com')
    .setExpirationTime('6h')
    .setIssuedAt()
    .sign(privateKey);
  console.log(token);
})();

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

&lt;/div&gt;



&lt;p&gt;The logged token should 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;eyJhbGciOiJSUzI1NiJ9.eyJteUNsYWltIjp0cnVlLCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiYXNkZiIsImF1ZCI6Im15VXNlciIsImV4cCI6MTYyNjg5OTM3MCwiaWF0IjoxNjI2ODc3NzcwfQ.z7RSsY34eyO_sOsebR92M7P2piqoP9vRDw0kp2VUCkU-2ZcGeA2Jvf4GpJDSwmjxSuXSptYwnF-Qvw9A7hb6BH6XmH9ZG3bFLR-UEUjqjAKL5LRleh3EKES2LqVvng89p9xzFsDePTyzzVmc4yWV0fGC1-lMTLAmnDXxhRFIZZdyBbtHoxt7bmgrdCkk8jV0qVy-SoxWb0KvC8A24Pkkb7eWAS1CQDwVxBTWJDa9ixc0-eKSt2xtzw6jL8o_bkoAHJV2Zk1Cu04752Z9eAExdNq3zI6_wQkwap44MR0kpNF2pMPZz6kNLEUECt_QAzobV7WKYuPtkLLKN_67P-OaLg

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

&lt;/div&gt;



&lt;p&gt;The payload (which is anything custom that I want to be added to the JWT) is the piece that looks like &lt;code&gt;{ myClaim: true }&lt;/code&gt;. The server gets to decide what it wants to add, and that payload will be available after verifying the JWT in another server. The other claims that are set are &lt;code&gt;iss&lt;/code&gt; (Issuer), &lt;code&gt;sub&lt;/code&gt; (Subject), &lt;code&gt;aud&lt;/code&gt; (Audience), &lt;code&gt;exp&lt;/code&gt; (Expiration Time), and &lt;code&gt;iat&lt;/code&gt; (Issued At). The protected header is pretty self-explanatory but there is &lt;a href="https://datatracker.ietf.org/doc/html/rfc7519#section-5"&gt;more information&lt;/a&gt; and &lt;a href="https://datatracker.ietf.org/doc/html/rfc7519#section-3.1"&gt;an example&lt;/a&gt; in the RFC.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iss&lt;/code&gt; should be set to your service’s URL. For example, &lt;a href="https://crowauth.com/"&gt;Crow Authentication&lt;/a&gt; sets the Issuer to &lt;code&gt;https://api.crowauth.com&lt;/code&gt; which is the API URL. This claim is optional.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sub&lt;/code&gt; should uniquely identify the principal that is the subject of the JWT. This could be a user’s ID or email address. This claim is optional.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aud&lt;/code&gt; should name the intended recipient of the JWT. For example, the application server that is requesting it. This claim is optional.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;exp&lt;/code&gt; represents the time at which the JWT should no longer be deemed valid. This claim is optional, but I highly recommend using it. For security reasons it is not a good idea to spit out a JWT that will forever validate the holder as being who they are. We need to either force the user to reauthenticate or use refresh tokens. Information abounds about reasons for using expiration claims and refresh tokens.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iat&lt;/code&gt; simply claims when the JWT was signed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying a JWT
&lt;/h2&gt;

&lt;p&gt;Remember that JWKS that we created a couple of sections ago? Now we get to use it. For the sake of keeping this guide focused on JWTs I am going to assume that either A) you have taken the time to create a working authentication server that can provide JWTs and hosts the JWKS (while this might require a decent amount of time to get set up, it is ultimately what we’re going for here) or B) you do not have a server set up. Situation A is best, but if you are limited on time or simply want to read code then I will provide small scripts to show the concepts working.&lt;/p&gt;

&lt;p&gt;At a high level, we need to grab the distributed public key then verify the JWT provided to the application server, so under normal circumstances, this code would be run by an application server to verify the identity of the client. In the example, I will be using the JWKS that I have referenced a couple of times throughout this guide. Please substitute the URL for the appropriate JWKS which corresponds to the private key used by the authentication server that signed the JWT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { createRemoteJWKSet } = require('jose/jwks/remote');
const { jwtVerify } = require('jose/jwt/verify');

const jwks = createRemoteJWKSet(new URL('https://crowauth.com/v1/jwks.json'));
const { payload, protectedHeader } = await jwtVerify(jwt, jwks); // The jwt variable needs to be passed in from somewhere; cookie, hard coded, parameter, etc.

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

&lt;/div&gt;



&lt;p&gt;What happens here, is that &lt;code&gt;jose&lt;/code&gt; pulls the JWKS, finds the key based on &lt;code&gt;use&lt;/code&gt; being &lt;code&gt;sig&lt;/code&gt;, &lt;code&gt;key_ops&lt;/code&gt; including &lt;code&gt;verify&lt;/code&gt;, and some user-provided options. It then uses the JWK found from the JWKS to verify that the signature on the JWT did in fact come from the correct private key/authentication server. &lt;code&gt;jwtVerify&lt;/code&gt; can also &lt;a href="https://github.com/panva/jose/blob/main/docs/interfaces/jwt_verify.JWTVerifyOptions.md"&gt;take in which claims to verify&lt;/a&gt; like the Issuer or Audience. The &lt;code&gt;payload&lt;/code&gt; returned will contain the application-specific claims or information provided while signing the JWT. Based on my previous signing example, the &lt;code&gt;payload&lt;/code&gt; would be &lt;code&gt;{ myClaim: true }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In case you do not have a remote JWKS set up and the rest of the authentication server ready, here is a script that shows the same concept but at a local level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(async () =&amp;gt; {
  const { publicKey, privateKey } = await generateKeyPair('RS256');
  // https://nodejs.org/api/crypto.html#crypto_class_keyobject
  const publicKeyString = publicKey.export({
    type: 'pkcs1',
    format: 'pem',
  });
  const privateKeyString = privateKey.export({
    type: 'pkcs1',
    format: 'pem',
  });

  const publicJwk = await fromKeyLike(publicKey);

  const token = await new SignJWT({
      myClaim: true,
    })
    .setProtectedHeader({
      typ: 'JWT',
      alg: 'RS256',
    })
    .setIssuer('https://example.com')
    .setSubject('asdf')
    .setAudience('myUser')
    .setExpirationTime('6h')
    .setIssuedAt()
    .sign(privateKey);

  const parsedJwk = await parseJwk(publicJwk, 'RS256'); // For the sake of the example, parse the JWK instead of simply using the generated publicKey
  const { payload, protectedHeader } = await jwtVerify(token, parsedJwk);
  console.log(protectedHeader);
  console.log(payload);
})();

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

&lt;/div&gt;



&lt;p&gt;While &lt;code&gt;parseJwk&lt;/code&gt; is not entirely necessary (we could just use the &lt;code&gt;publicKey&lt;/code&gt; directly), this shows the same idea of verifying a JWT based on a JWK.&lt;/p&gt;

&lt;h2&gt;
  
  
  The End
&lt;/h2&gt;

&lt;p&gt;You’ve done it. Good job. Hopefully, this guide made learning about JWTs with Javascript easy and you did not need to do much Googling on the side. At the beginning of the guide, I mentioned that JWTs are a popular form of authenticating users for web applications. Did you notice that I did not once talk about JWTs in the context of web applications? Whoops.&lt;/p&gt;

&lt;p&gt;Understanding JWTs and how to operate with them was enough for this guide. I will be putting out another guide soon about using JWTs with web applications. That guide will cover how to authenticate users, where JWTs should be stored, and the relationships between the authentication server, browser, and application servers. Whenever it is written and published, I will edit this post with a link to it.&lt;/p&gt;

&lt;p&gt;If you enjoyed this guide and it was helpful, please let me know somehow. Send an email, tweet at me, comment on one of the cross-posts (if you’re not reading this in &lt;a href="https://thomasstep.com/"&gt;my personal blog&lt;/a&gt;), whatever floats your boat. If it wasn’t helpful, there are errors, or you have suggestions, the same rule applies. If you need authentication for a new web application and this is your first introduction to it, might I suggest saving yourself the hassle and going with an off-the-shelf solution like &lt;a href="https://crowauth.com/"&gt;Crow Authentication&lt;/a&gt;? Until next time!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Three New Things I Tested To Improve My SEO</title>
      <dc:creator>Thomas Step</dc:creator>
      <pubDate>Thu, 15 Jul 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/thomasstep/three-new-things-i-tested-to-improve-my-seo-3lp8</link>
      <guid>https://dev.to/thomasstep/three-new-things-i-tested-to-improve-my-seo-3lp8</guid>
      <description>&lt;p&gt;I’m writing this post with a little hesitancy. The title might read like generic clickbait, but I hope to inject some originality into this subject. What you probably clicked on to read about is further on down past my rant. Feel free to skip ahead.&lt;/p&gt;

&lt;p&gt;When I first started hearing about SEO I imagined it as a large abstract construct. The quick and dirty explanation that I go back to for myself is that SEO is simply the process of ranking higher on search engines. That’s it. There are tons of hacky methods and instructors out there that pawn off regurgitated content as new courses that will “help buyers and bloggers make more money.” That’s not what I’m trying to do here. I remember reading through posts about finding keywords to write about, investigating a niche with &lt;a href="https://ahrefs.com/"&gt;ahrefs&lt;/a&gt;, and writing consistently. After reading these “tricks” for the Nth time, I stopped clicking on SEO articles. I prefer to simply write about what I want and what I wish was out there when I was looking for guidance or to solve a problem.&lt;/p&gt;

&lt;p&gt;In my opinion, there is way too much recycled content out there “written” by people who claim to have subject knowledge, but who have never practiced or seen success with their methods. I’ll argue that they just want a few quick clicks and use them to get a few easy pennies. &lt;a href="https://twitter.com/swyx"&gt;Swyx&lt;/a&gt; wrote about this class of authors as &lt;a href="https://www.swyx.io/meta-creator-ceiling/"&gt;meta-creators&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All that just to say, that I hope these tips are new and original to you. There have only been three jumps in my traffic history so far and each one corresponds to a different tip. I saw more success with my traffic and rankings after I completed these, so maybe they can help you too. But who knows 🤷‍♂️, I’m no SEO expert.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Canonical URLs
&lt;/h3&gt;

&lt;p&gt;When I first started to cross-post to &lt;a href="https://dev.to/thomasstep"&gt;DEV.to&lt;/a&gt; I did not realize that they offered canonical URLs. I’m pretty sure I didn’t even know what they were at the time. Instead, I used to manually add in my page’s URL as a backlink to try and drive traffic to me, but that did not do near as much as a &lt;code&gt;link&lt;/code&gt; tag for a canonical URL. I know that adding canonical URLs to my DEV.to posts worked because my article views there stopped increasing as rapidly, and I saw more organic traffic being driven to me about a week after I added them to my existing posts. I guess that it just took a few days for Google to realize where my original content was posted before it started ranking me above the DEV.to versions of my articles. Most publishers (like DEV.to and Medium) have a setting for an article’s canonical URL. However, if you have access to the cross-posted article’s HTML you can add the following in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="canonical" href="https://your.site/original-article"&amp;gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Changed To A &lt;code&gt;.com&lt;/code&gt; Domain Name
&lt;/h3&gt;

&lt;p&gt;I have heard that Google does not care about the TLD, which is why I initially used a &lt;code&gt;.dev&lt;/code&gt; TLD. I switched TLDs for a different reason, but whenever I did switch, I started getting a noticeable jump in organic traffic. Since Google themselves say that it doesn’t affect rankings, I am left to take a shot in the dark for why this worked. I guess that my traffic was less affected by Google and more affected by people. My thought is that people trust a &lt;code&gt;.com&lt;/code&gt; TLD more than a &lt;code&gt;.dev&lt;/code&gt; one, which causes them to click on it before other options. While Google might not care about TLD, they do care about clicks, which results in a higher ranking. People and their behaviors also play a part in SEO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leave Breadcrumbs Every Where You Go
&lt;/h3&gt;

&lt;p&gt;Another significant boost to my organic traffic came after I reorganized my site’s pages. What is now my &lt;code&gt;/blog&lt;/code&gt; page used to be my homepage. I reorganized it to become my &lt;code&gt;/blog&lt;/code&gt; page and then I created my home page (&lt;code&gt;/&lt;/code&gt;) based on my old &lt;code&gt;/about&lt;/code&gt; page. Again, I did this not for SEO, but my own reasons. However, after I did it, I received my latest surge in organic traffic. My only guess as to why this worked was because Google could now follow breadcrumbs to all of my pages within each other. All of my blog posts are under URLs like &lt;code&gt;/blog/this-is-a-blog-post&lt;/code&gt;. My thought is that Google wants breadcrumbs to each path and since I did not have a &lt;code&gt;/blog&lt;/code&gt; page before, there was a gap between my home page and my blog posts.&lt;/p&gt;

&lt;p&gt;I hope that helps and I hope someone can benefit from my observations. It definitely helped me. Feel free to reach out if you have any other questions!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
