<?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: Chris Armstrong</title>
    <description>The latest articles on DEV Community by Chris Armstrong (@chrisarmstrong).</description>
    <link>https://dev.to/chrisarmstrong</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%2F211998%2F4d1a6486-a870-4435-8d63-e62d87fa113e.jpeg</url>
      <title>DEV Community: Chris Armstrong</title>
      <link>https://dev.to/chrisarmstrong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chrisarmstrong"/>
    <language>en</language>
    <item>
      <title>Visual Regression Testing with Storybook 7</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Sat, 13 May 2023 07:38:49 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/visual-regression-testing-with-storybook-7-3gho</link>
      <guid>https://dev.to/chrisarmstrong/visual-regression-testing-with-storybook-7-3gho</guid>
      <description>&lt;p&gt;Storybook 7 has deprecated the &lt;a href="https://storybook.js.org/addons/@storybook/addon-storyshots"&gt;storyshots plugin&lt;/a&gt;, which was commonly used to set up a &lt;a href="https://storybook.js.org/docs/ember/writing-tests/visual-testing"&gt;visual regression testing workflow&lt;/a&gt; with &lt;a href="https://github.com/americanexpress/jest-image-snapshot"&gt;jest-image-snapshot&lt;/a&gt; (as an alternative to &lt;a href="https://www.chromatic.com/"&gt;their paid Chromatic&lt;/a&gt; service, which offers storybook hosting and visual regression testing).&lt;/p&gt;

&lt;p&gt;However, at the time of writing (13/5/2023), Storybook are yet to update their documentation to say this is deprecated, with it only being &lt;a href="https://github.com/storybookjs/storybook/issues/22263"&gt;mentioned in Github issues&lt;/a&gt;, and no alternative workflow documented with their new &lt;a href="https://storybook.js.org/addons/@storybook/test-runner"&gt;test-runner&lt;/a&gt; yet. It is even broken in v7, with recommendations to &lt;a href="https://github.com/storybookjs/storybook/issues/22142"&gt;use workarounds&lt;/a&gt; instead of a mainline fix yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The new test-runner functionality uses &lt;a href="https://playwright.dev/"&gt;&lt;code&gt;playwright&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://jestjs.io/"&gt;&lt;code&gt;jest&lt;/code&gt;&lt;/a&gt; under the hood to run in-browser tests against each of your stories. We can hook this new functionality and run a visual snapshot test with &lt;a href="https://github.com/americanexpress/jest-image-snapshot"&gt;&lt;code&gt;jest-image-snapshot&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each story in your storybook is transformed into a test using &lt;a href=""&gt;&lt;code&gt;babel&lt;/code&gt;&lt;/a&gt;, and you can add render assertions with a &lt;code&gt;play()&lt;/code&gt; function in your story.&lt;/p&gt;

&lt;p&gt;We will customise the underlying test-runner configuration to add a post-test hook to create a snapshot with &lt;code&gt;jest-image-snapshot&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="nb"&gt;install &lt;/span&gt;jest-image-snapshot @storybook/test-runner &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a test-runner config
&lt;/h2&gt;

&lt;p&gt;Storybook have thankfully provided useful jest configuration hooks we can use to install &lt;code&gt;jest-image-snapshot&lt;/code&gt; and run it after each generated story test.&lt;/p&gt;

&lt;p&gt;Create a file in your storybook directory at &lt;code&gt;.storybook/test-runner.ts&lt;/code&gt; with this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TestRunnerConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/test-runner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toMatchImageSnapshot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jest-image-snapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TestRunnerConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;toMatchImageSnapshot&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;postRender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add a post-render delay in case page is still animating&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&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;screenshot&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatchImageSnapshot&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add a babel configuration
&lt;/h2&gt;

&lt;p&gt;If you don't already have a &lt;code&gt;.babelrc&lt;/code&gt; (or equivalent), you will need to create one (e.g. if you use &lt;code&gt;swc&lt;/code&gt; already, you will still need to create a babel configuration as unfortunately Storybook test-runner needs it to read your&lt;br&gt;
&lt;code&gt;.stories.tsx&lt;/code&gt; files and create jest unit tests out of them).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;.babelrc&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"presets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@babel/preset-typescript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@babel/preset-env"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running visual regression tests
&lt;/h2&gt;

&lt;p&gt;The visual regression tests can be setup in your &lt;code&gt;package.json&lt;/code&gt; &lt;code&gt;scripts&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test-storybook"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yarn run storybook-test"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When running the &lt;code&gt;test-runner&lt;/code&gt; you need an instance of storybook running in the background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn run storybook &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then run &lt;code&gt;test-storybook&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn run test-storybook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Establish a baseline
&lt;/h3&gt;

&lt;p&gt;When you run your visual regression tests for the first time, &lt;code&gt;storybook-test&lt;/code&gt; will create a baseline. This will save image files under &lt;code&gt;__image_snapshot__&lt;/code&gt; in your source code folder, which you should check in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating your snapshots
&lt;/h3&gt;

&lt;p&gt;You will need to update your snapshots when there has been a valid change to the layout of your story or there has been a regression.&lt;/p&gt;

&lt;p&gt;When a regression is found, the test-runner will fail and it will give you an image path on your filesystem you can reference to see what the regression was.&lt;/p&gt;

&lt;p&gt;Once you have fixed and validated your stories and are satisfied they are working correctly, you can update your&lt;br&gt;
baseline snapshots by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn run test-storybook &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and check in the resulting changes to git.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running visual regression tests in CI/CD
&lt;/h3&gt;

&lt;p&gt;You can use the method above (running storybook) to execute the visual regression tests in a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;If you build your storybook for deployment to a server, you can also execute the tests against that server or against the built artifacts with a local http server.&lt;/p&gt;

&lt;p&gt;For example, to run them locally after building the storybook with &lt;code&gt;yarn build&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add wait-on concurrently http-server &lt;span class="nt"&gt;-D&lt;/span&gt;
npx concurrently &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; first &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"SB,TEST"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"magenta,blue"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"npx http-server storybook-static --port 6006 --silent"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"npx wait-on tcp:6006 &amp;amp;&amp;amp; yarn test-storybook
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>API Mismatch: Why bolting SQL onto noSQL is a terrible idea</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Thu, 12 Jan 2023 00:20:21 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/api-mismatch-why-bolting-sql-onto-nosql-is-a-terrible-idea-2a54</link>
      <guid>https://dev.to/chrisarmstrong/api-mismatch-why-bolting-sql-onto-nosql-is-a-terrible-idea-2a54</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR; Use abstractions that are designed to hide the complexity of the underlying technology, not those that expose its limitations and do not match its semantic model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I recently started experimenting with Remix for a new side-project idea using a &lt;a href="https://remix.run/docs/en/v1/tutorials/blog" rel="noopener noreferrer"&gt;tutorial with a starter template&lt;/a&gt;, that happened to include &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt; (a SQL data access layer, self-described TypeScript ORM) with sqlite as its database.&lt;/p&gt;

&lt;p&gt;I've not had that much to do with Prisma, but its API seems self-explanatory. The automatic schema management, and the schema migration feature, seem like an excellent way to manage new tables and indexes you add to your code through the deployment cycle (something many ORMs don't give you).&lt;/p&gt;

&lt;p&gt;This got me thinking about DynamoDB (a database I use day to day, and for which I maintain &lt;a href="https://github.com/chris-armstrong/dynaglue" rel="noopener noreferrer"&gt;dynaglue&lt;/a&gt;, a single-table mapping layer for TypeScript/JavaScript), and made me wonder if a DynamoDB adapter existed. I stumbled across &lt;a href="https://github.com/prisma/prisma/issues/1676" rel="noopener noreferrer"&gt;this GitHub issue&lt;/a&gt;, and arrogantly shot off &lt;a href="https://twitter.com/ckarmstrong/status/1611611191532736512" rel="noopener noreferrer"&gt;this tweet&lt;/a&gt; not expecting any replies.&lt;/p&gt;

&lt;h2&gt;
  
  
  But DynamoDB supports SQL
&lt;/h2&gt;

&lt;p&gt;As &lt;a href="https://twitter.com/pj_naylor/status/1611876462617067520?s=20" rel="noopener noreferrer"&gt;one commenter pointed out&lt;/a&gt;, DynamoDB supports PartiQL, which helps provide something of a SQL like interface to DynamoDB, which is completely true, and on the face of it, would be beneficial for creating a DynamoDB adapter for ORM layers like Prisma.&lt;/p&gt;

&lt;p&gt;What that conceals from the discussion, however, is that the supported subset of PartiQL is only that which maps directly onto DynamoDB design. &lt;em&gt;That subset is no-where near the set of features that are considered standard in a SQL database.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For starters, APIs like &lt;code&gt;groupBy&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;aggregate&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; and &lt;code&gt;updateMany&lt;/code&gt; have no DynamoDB equivalent (and hence no PartiQL equivalent). They can be simulated by an adapter, but that means Prisma is no longer behaving as an ORM, but is inheriting responsibilities of the database itself.&lt;/p&gt;

&lt;p&gt;Much worse, is the fact that PartiQL will supports parts of &lt;code&gt;findUnique&lt;/code&gt;, &lt;code&gt;findMany&lt;/code&gt;, etc., but unless the developer specifies a primary key field in their &lt;code&gt;WHERE&lt;/code&gt; clause, it will &lt;em&gt;fallback to using a scan&lt;/em&gt; on the table. A scan can be reasonably fast for a small number of items, but becomes linearly slower on larger tables.&lt;/p&gt;

&lt;p&gt;Developers working with relational databases will be well aware of this problem - a query that performs adequately in development (because there are few items in your table) will perform atrociously in production, the fix being simply to add an index. This is the case for DynamoDB as well, but there are limits on how you can index existing documents (you will need a data migration if you need to index a nested value).&lt;/p&gt;

&lt;p&gt;If pay-per-request pricing is used with DynamoDB, this scan can also be &lt;strong&gt;expensive&lt;/strong&gt;, as users are charged per items scanned in the partition, not what is returned in the response. (When provisioned capacity is used, the developer is more likely to saturate the available read units, especially if parallel scanning is deployed). A relational database will just degrade in overall performance until it is scaled upwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you have a plan?
&lt;/h2&gt;

&lt;p&gt;Relational databases are smart enough to work out what indexes they have available to them and "plan" how to execute a query based on what is available. This means they may be able to satisfy the entire WHERE clause using an index, fall back to scanning and index and then filtering the results, combining and filtering the results of looking up or scanning multiple indexes, or scanning and filtering the entire table.&lt;/p&gt;

&lt;p&gt;The decisions a query planner makes about what indexes to use or not, comes down to both its knowledge of those indexes, and the statistics collected on the use of those indexes. This allows for a relatively sophisticated query execution based on the database engine's initimate knowledge of the underlying data storage &lt;em&gt;and its ongoing usage&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The statistics a SQL engine can store includes how long it takes to access an index based on certain query parameters, how much data is populated in that index, etc., which are all used to inform the query planner's decision making process.&lt;/p&gt;

&lt;p&gt;On the other hand, a DynamoDB database provides none of this to PartiQL (its not even available to client applications). At best, it can identify indexed fields in the WHERE clause, and select indexes that may help to speed up the query by reducing the set of results to scan.&lt;/p&gt;

&lt;p&gt;However it won't be able to make any sophisticated decisions on how to use those indexes. It does not have any information about how sparsely those indexes are populated, relative lookup times, etc. that a relational engine would normally use to speed up execution. In most cases, its going to fall back to a full database scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a DynamoDB scan is not the same as a relational SQL scan
&lt;/h2&gt;

&lt;p&gt;In my mind, this isn't even the worst part, which boils down to a fundamental difference is how data is stored and retrieved in DynamoDB vs relational databases.&lt;/p&gt;

&lt;p&gt;In relational engines, the query is executed very close to the where the data is stored (typically on the same node). This means that a scan can be relatively fast, even over a large number of items, because the retrieval and filtering is performed on the database node before a response is streamed back to the application.&lt;/p&gt;

&lt;p&gt;On the other hand, DynamoDB will perform filtering on the node, but it does it in fixed size chunks. &lt;em&gt;The application must wait an entire round-trip over the network to request a scan on the next part of the index&lt;/em&gt;. This problem is made much worse when most of the index's elements are filtered out.&lt;/p&gt;

&lt;p&gt;The relational database only has to wait between the memory and the storage drives (or if splitting over a cluster, for batches of results to come back from each node). Each round-trip from the application and the database can be optimised to return as much as possible, which is not possible with DynamoDB.&lt;/p&gt;

&lt;p&gt;(The limits on scanning can be ameleorated using a &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.ParallelScan" rel="noopener noreferrer"&gt;parallel scan&lt;/a&gt;, but this is best done as a deliberate implementation detail where the potential costs and resource usage are considered beforehand, not as an off-hand consequence of an untested query).&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstractions and depth of understanding
&lt;/h2&gt;

&lt;p&gt;The value in a layer like Prisma is that it abstracts away the hairier details of a database engine from the developer, leaving the indexing and query writing to the underlying adapter. Developers can focus on business logic instead of technical details.&lt;/p&gt;

&lt;p&gt;When the database layer underlying the Prisma API cannot satisfy the majority of the API, or runs with unexpected performance and cost issues when the supported APIs are found, the value underlying the abstraction is lost. The developer now has to understand the limits of DynamoDB and be careful what they query to make sure it is optimally using indexes.&lt;/p&gt;

&lt;p&gt;This is something where a relational database is more forgiving (just add an index when performance degrades). For developers that does understand the limits of DynamoDB, they will then be needing to migrate data to support new indexes and "drop-down" to the DynamoDB interface itself when they cannot perform what they need in the database adapter.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I did mention before that it would theoretically be possible to build the missing functionality into the Prisma adapter itself. There may be value in building a SQL like interface over DynamoDB, but you're sacrificing one of the reasons for using it in the first place - consistency of performance under load - while absorbing the cost of maintaining what would normally be a battle-tested component of the database itself)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the platform, not the abstraction
&lt;/h2&gt;

&lt;p&gt;Does this mean you should just use a relational database?&lt;/p&gt;

&lt;p&gt;This is where I think technology selection is important. NoSQL databases like DynamoDB have the advantage of consistent performance and automatic scaling under increasing load, which is not something that can normally be said of relational databases.&lt;/p&gt;

&lt;p&gt;The trade-off, of course, is learning how to use the technology, ensuring that your access patterns are well suited for NoSQL databases, and designing your data storage to optimise queries with those access patterns. You need to understand the limits of the platform and use other systems to fill those requirements not fulfilled by DynamoDB.&lt;/p&gt;

&lt;p&gt;That said, if your team's experience is with relational databases, or your application is not suited for NoSQL workloads, SQL databases offer an excellent solution backed by decades of refinement, flexible storage options and well understood limits and scaling requirements. You're in safe hands backed by broad institutional knowledge of the technology (you'll never have a problem finding a developer who has worked with SQL).&lt;/p&gt;

&lt;p&gt;Throw Prisma in the mix, and you have a well-worn abstraction (with the additional benefits of schema management that it brings) that will serve your needs well and let your developers focus on what matters.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Package your NodeJS Lambda functions individually with esbuild for faster cold-start times</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Wed, 05 Jan 2022 01:08:31 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/package-your-nodejs-lambda-functions-individually-with-esbuild-for-faster-cold-start-times-4ijj</link>
      <guid>https://dev.to/chrisarmstrong/package-your-nodejs-lambda-functions-individually-with-esbuild-for-faster-cold-start-times-4ijj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Learn about packaging your NodeJS-based AWS Lambda functions with esbuild for improved cold start times and reduced deployment package size&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was &lt;a href="https://www.chrisarmstrong.dev/posts/package-aws-lambda-nodejs-functions-individually-with-esbuild-for-faster-cold-start"&gt;originally posted&lt;/a&gt; on my &lt;a href="https://www.chrisarmstrong.dev"&gt;development blog&lt;/a&gt;, where you can find other articles about TypeScript, AWS, Lambda and open source.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/serverless/sam/"&gt;AWS SAM&lt;/a&gt; is a great way to package and deploy your AWS Lambda functions, but as your project grows, it's easy to become frustrated with increasing deployment times from an ever-growing deployment package containing all your functions and NodeJS dependencies.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.serverless.com/"&gt;serverless framework&lt;/a&gt; supports an excellent workflow with the &lt;a href="https://github.com/serverless-heaven/serverless-webpack"&gt;serverless-webpack&lt;/a&gt; plugin to package your functions individually with webpack. For AWS SAM, it's probably worth checking out the &lt;a href="https://github.com/graphboss/aws-sam-webpack-plugin"&gt;aws-sam-webpack-plugin&lt;/a&gt;, but I've found it far simpler to configure esbuild to perform the same task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why package my code using a bundler?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved cold start time&lt;/strong&gt; - &lt;a href="https://mikhail.io/serverless/coldstarts/aws/#does-package-size-matter"&gt;smaller deployment packages generally means less time copying your deployment package to the AWS Lambda container each time it has to be started&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript support&lt;/strong&gt; - NodeJS doesn't support TypeScript natively, so converting it to JavaScript can be done as part of a bundilng step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tree-shaking and reduced deployment package size&lt;/strong&gt; - a bundler will only include the node dependencies your function imports, and if enabled correctly for &lt;a href="https://webpack.js.org/guides/tree-shaking/"&gt;tree-shaking&lt;/a&gt;, your overall bundle size will be reduced to just the blocks of code that are needed (which is great for multi-functional libraries like &lt;a href="https://lodash.com"&gt;lodash&lt;/a&gt; which are rarely used as a whole).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;p&gt;Using a bundler does come with some downsides, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increased complexity&lt;/strong&gt; - bundling your code means more steps before running your code locally using &lt;code&gt;aws sam local&lt;/code&gt; and more deployment time steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inceased packaging time&lt;/strong&gt; - as your functions are packaged individually, a unique deployment package is generated for each one, instead of uploading a single deployment package with all of your functions. However, this may still be acceptable as each function's deployment package will be considerably smaller after bundling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor stack trace support&lt;/strong&gt; - getting decent stack trace support with bundled code can be very difficult, especially for exceptions. In many cases, you'll still be debugging by &lt;code&gt;console.log()&lt;/code&gt; or guessing based on the function names in your stack trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module support&lt;/strong&gt; - some node modules are not compatible with bundling, either because they assume the existence of the &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;node_modules&lt;/code&gt; folder (looking for filesystem artefacts in their code), or they are trying to perform auto-instrumentation for telemetry (which bundling interferes with).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  esbuild vs webpack
&lt;/h3&gt;

&lt;p&gt;I've used &lt;a href="https://webpack.js.org/"&gt;webpack&lt;/a&gt; before for bundling, and while it is a mature and flexible bundler, you may run into problems with its memory usage and execution when you use it on very large projects with dozens of AWS Lambda functions. This is because it is written in JavaScript and must execute the same bundling process for each Lambda function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; is a newer bundler built in Go, which is gaining considerable popularity for its speedy execution time and simple configuration, as well as removing the need for Babel with its native TypeScript support. However, it should be noted it has less features and is less mature than webpack (and probably always will because it is deliberately targeting a reduced feature set). &lt;/p&gt;

&lt;p&gt;I've chosen esbuild because I've found it mature enough to use in production for large, established AWS Lambda based applications, and it reduced bundling times from over 5 minutes to less than 30 seconds. &lt;/p&gt;

&lt;h2&gt;
  
  
  Using esbuild with AWS SAM
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Configuring your project
&lt;/h3&gt;

&lt;p&gt;In this setup, we will bypass &lt;code&gt;sam build&lt;/code&gt; and set up an esbuild bundling step that must be run before every &lt;code&gt;sam package&lt;/code&gt;/&lt;code&gt;sam deploy&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install esbuild
&lt;/h4&gt;

&lt;p&gt;First get esbuild installed with &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i esbuild &lt;span class="nt"&gt;-D&lt;/span&gt;

&lt;span class="c"&gt;# OR&lt;/span&gt;

yarn add esbuild &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Source code layout
&lt;/h4&gt;

&lt;p&gt;Next structure your function entry points to be hosted in individual directories - this helps with ensuring that bundled output for each function gets its own directory, and hence can be deployed individually by SAM.&lt;/p&gt;

&lt;p&gt;e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/say_hello/index.ts
src/send_email/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I've assumed TypeScript here, but you can use JavaScript by simply changing the file extension. Remember that you should have &lt;code&gt;typescript&lt;/code&gt; installed in your project and a &lt;code&gt;tsconfig.json&lt;/code&gt; set up for your Node version if you're transpiling TS).&lt;/p&gt;

&lt;p&gt;If you have any other shared code, it should be put into another directory e.g &lt;code&gt;/shared&lt;/code&gt; or &lt;code&gt;/util&lt;/code&gt; and simply imported.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup esbuild configuration
&lt;/h4&gt;

&lt;p&gt;Although esbuild can be run from the command line, a quick JavaScript configuration file is easier to maintain and read with the number of options we will use.&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;fs&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;fs&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;path&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;path&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;esbuild&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;esbuild&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;functionsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`src`&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;outDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`dist`&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;entryPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functionsDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;functionsDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.ts`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;esbuild&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bundle&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="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;outbase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;functionsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline&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 assumes your Lambda function entry points are located in &lt;code&gt;./src/&amp;lt;function_name&amp;gt;/index.ts&lt;/code&gt; and will be built to &lt;code&gt;./dist/&amp;lt;function_name&amp;gt;/index.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you would prefer to do the above using the command line, the following &lt;code&gt;esbuild&lt;/code&gt; line should be equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx esbuild &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bundle&lt;/span&gt; src/&lt;span class="k"&gt;*&lt;/span&gt;/index.ts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dist &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outbase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;src/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sourcemap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;inline &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Setting up your AWS SAM template
&lt;/h4&gt;

&lt;p&gt;Each function in your AWS SAM template will need to be updated to use the new package layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;HelloFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist/say_hello&lt;/span&gt;
    &lt;span class="c1"&gt;# assumes your function is at `src/say_hello/index.ts` or &lt;/span&gt;
    &lt;span class="c1"&gt;# `src/say_hello/index.js` with an exported entry point &lt;/span&gt;
    &lt;span class="c1"&gt;# function called `handler`&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.handler&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;SendFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist/send_email&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.handler&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building and deploying your function
&lt;/h3&gt;

&lt;p&gt;You can now build and deploy your function by running esbuild before each sam package and sam deploy step i.e.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node esbuild.js 
sam package
sam deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Your bundled code can be executed locally, but you need to run &lt;code&gt;node esbuild.js&lt;/code&gt; before you call &lt;code&gt;sam local invoke&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you want to re-run esbuild in watch mode, you can modify your &lt;code&gt;esbuild.js&lt;/code&gt; accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;esbuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
  &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;watch&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;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--watch&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;and run &lt;code&gt;node esbuild.js --watch&lt;/code&gt; in the background (or add &lt;code&gt;--watch&lt;/code&gt; to the command line variant given in Setup esbuild configuration).&lt;/p&gt;

</description>
      <category>aws</category>
      <category>javascript</category>
      <category>esbuild</category>
      <category>lambda</category>
    </item>
    <item>
      <title>SQS Queues as an EventBridge Rule Target (with CloudFormation)</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Thu, 02 Dec 2021 06:40:28 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/sqs-queues-as-an-eventbridge-rule-target-3d2g</link>
      <guid>https://dev.to/chrisarmstrong/sqs-queues-as-an-eventbridge-rule-target-3d2g</guid>
      <description>&lt;p&gt;AWS offers a &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html#eb-console-targets"&gt;dizzying array of targets for EventBridge Rules&lt;/a&gt;, but even if you set them up through the console, the documentation for each target type can be a bit light.&lt;/p&gt;

&lt;p&gt;SQS, being an early AWS service, can come with some extra quirks that make integrations a bit trickier, especially when it comes authorization or configuration.&lt;/p&gt;

&lt;p&gt;In this article, I show you how to set up SQS queues and SQS FIFO queues as a target of an Event Bridge Rule using CloudFormation syntax. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: I've assumed you're familiar with EventBridge events and SQS, and just want to get up and running.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Event Bridge to SQS Queue - the simple case
&lt;/h2&gt;

&lt;p&gt;There is nothing special in the EventBridge rule - just the target ARN of the Queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;MyQueueTarget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Events::Rule&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Configure the event pattern to filter your events (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html)&lt;/span&gt;
      &lt;span class="na"&gt;EventPattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.mycompany.events"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;detailType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CustomerPurchase"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;Targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyQueue.Arn&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueueTarget&lt;/span&gt; &lt;span class="c1"&gt;# set as needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need a role ARN because the permissions are handled using queue policies&lt;/p&gt;

&lt;h2&gt;
  
  
  Queue and Queue Policy
&lt;/h2&gt;

&lt;p&gt;Define your queue - it requires no special properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;MyQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::Queue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and define a queue policy, which will permit EventBridge to write events to your queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;MyQueueEventBridgePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::QueuePolicy&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2012-10-17&lt;/span&gt;
        &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt;
            &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;events.amazonaws.com&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQS:SendMessage&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyQueue.Arn&lt;/span&gt;
      &lt;span class="na"&gt;Queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyQueue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can further restrict the policy with a condition key limiting access to the event rule that invoked the policy (you shouldn't consider this optional, otherwise you're granting access to any EventBridge rule):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;         &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="pi"&gt;-&lt;/span&gt;
            &lt;span class="s"&gt;...&lt;/span&gt;
            &lt;span class="s"&gt;Condition&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;ArnEquals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws:SourceArn"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="nv"&gt;MyQueueTarget.Arn&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Event Bridge to FIFO Queue - the complex case
&lt;/h2&gt;

&lt;p&gt;Similar to the normal SQS queue case, you define your event bridge rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;MyFifoQueueTarget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Events::Rule&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Configure the event pattern to filter your events (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html)&lt;/span&gt;
      &lt;span class="na"&gt;EventPattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.mycompany.events"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;detailType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CustomerPurchase"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;Targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyFifoQueue.Arn&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueueTarget&lt;/span&gt; &lt;span class="c1"&gt;# set as needed&lt;/span&gt;
          &lt;span class="na"&gt;SqsParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;MessageGroupId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myMessageGroupId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to SQS queues, you don't need a Role ARN, but you do need to specify a &lt;code&gt;MessageGroupId&lt;/code&gt;. This value is a fixed string - there is currently no support to make this dependent on a value in the incoming event, which means all your messages will be in the same &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html"&gt;message group&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Queue and Queue Policy
&lt;/h2&gt;

&lt;p&gt;Your queue is similar, but will need to be a FIFO queue, and have content-based deduplication turned on (this seems to mandatory to avoid silent failures):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;MyFifoQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::Queue&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FifoQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;ContentBasedDeduplication&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue policy is the same (see the previous section for an example).&lt;/p&gt;

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

&lt;p&gt;SQS &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-server-side-encryption.html"&gt;supports two types of encryption at rest&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSE-SQS&lt;/strong&gt; - SQS manages the encryption for you. This currently isn't available with CloudFormation, so I haven't tested its use with EventBridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSE-SQS&lt;/strong&gt; - KMS is used to perform encryption, either with a AWS-managed key or a Customer-Managed Key (CMK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transport encryption still relies on TLS, and IAM (via queue policies) is used to perform authorization and control access to the queue.&lt;/p&gt;

&lt;p&gt;Using the AWS-managed key with SQS (by specifying &lt;code&gt;KMSMasterKeyId: alias/aws/sqs&lt;/code&gt;) doesn't appear to work, and this is probably because specific access hasn't been granted to EventBridge to access the key, and probably can't be made to work because AWS managed keys do not let you edit their IAM key policy.&lt;/p&gt;

&lt;p&gt;A customer managed key may be able to be made to work - &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-troubleshooting.html#eb-sqs-encrypted"&gt;see this FAQ for an example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hopefully once SSE-SQS is available in CloudFormation, it should be simple matter of enabling it so encryption can be enabled in SQS when used as an EventBridge target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Processing messages
&lt;/h2&gt;

&lt;p&gt;The records that arrive in your SQS queue will have a body of the whole EventBridge event by default (&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#aws-properties-events-rule-target-properties"&gt;unless you configure your EventBridge rule to transform, or select part of the event, or pass a different JSON entirely using the InputTransformer/InputPath/Input properties&lt;/a&gt; - see &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html"&gt;this article&lt;/a&gt; for details on using &lt;code&gt;InputTransformer&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If you process your "EventBridge over SQS" events with AWS Lambda, your input event will look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"messageId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9c3aeeeef-6eee-4a18-9abc-89753969a233"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"receiptHandle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AQEBUjLMNIEkASgzMS9sSrfQdkZZa7tfH0sLH/KEXpr3P3c+Aen0WQ3x3gZoKXycvhkjU3F6vA+VwRky3JOCRxUN5o1d97y52Iqw0ID/HUflIXDoJWEUCRDUIRCONTEtbYW39EE5Fxxz+rc4YyBw5v8dlxCDtXdgdyEucUVIdQt4K+94YJdz3GTpQF2ASg0YdhRQvWQb4to+wfJLVW/C0f/cgY43zFTNHrRCSuRKBMNEUONSKntc0ubh7QHyBjNoIUC+2QXGq2ECdkBlBJ9BVDQZLXuObgjcoL1hi2XiLebTKCRT0Jo0rMEfs17giLHIFwy1ZrKeOqim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;5b2fcd39-5fd6-b4ef-51a6-7d5624ced5da&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;detail-type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;CustomerPurchase&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;com.mycompany.events&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;account&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;3829362938291&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;time&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;2021-12-02T06:01:09Z&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;region&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;resources&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;detail&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;orderNumber&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;2928&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws:sqs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventSourceARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sqs:us-east-1:3829362938291:my-queue.fifo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"awsRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't care about the EventBridge envelope, and would prefer to receive the raw &lt;code&gt;detail&lt;/code&gt; component of the message, the simplest input transformation is to set &lt;code&gt;InputPath&lt;/code&gt; to &lt;code&gt;$.detail&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;MyFifoQueueTarget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Events::Rule&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;....&lt;/span&gt;
      &lt;span class="s"&gt;Targets&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; 
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueueTarget&lt;/span&gt; &lt;span class="c1"&gt;# set as needed&lt;/span&gt;
          &lt;span class="s"&gt;...&lt;/span&gt;
          &lt;span class="na"&gt;InputPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$.detail&lt;/span&gt; &lt;span class="c1"&gt;# just send the event detail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is most useful when porting existing integrations (e.g. SNS -&amp;gt; SQS with raw message delivery), when you're using your own events, and/or when your event rule selects the Event Source and Event Detail Type such that you know the schema of the detail component without needing the EventBridge metadata to distinguish it.&lt;/p&gt;

</description>
      <category>sqs</category>
      <category>eventbridge</category>
      <category>aws</category>
      <category>cloudformation</category>
    </item>
    <item>
      <title>Testing node-fetch with jest in TypeScript</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Mon, 30 Aug 2021 23:16:21 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/testing-node-fetch-with-jest-in-typescript-13oo</link>
      <guid>https://dev.to/chrisarmstrong/testing-node-fetch-with-jest-in-typescript-13oo</guid>
      <description>&lt;p&gt;&lt;em&gt;(This was originally posted on my development blog at &lt;a href="https://chrisarmstrong.dev"&gt;chrisarmstrong.dev&lt;/a&gt;, so check it out for more JavaScript and AWS related content!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because I haven't seen it anywhere else, and because it was a bit tricky to set up, here is an example of testing some code that uses the &lt;a href="https://www.npmjs.com/package/node-fetch"&gt;node-fetch&lt;/a&gt; library with TypeScript.&lt;/p&gt;

&lt;p&gt;First install &lt;a href="https://www.npmjs.com/package/fetch-mock-jest"&gt;fetch-mock-jest&lt;/a&gt;, &lt;a href="https://www.wheresrhys.co.uk/fetch-mock/"&gt;fetch-mock&lt;/a&gt; and its types package &lt;code&gt;@types/fetch-mock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, set up your test like follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FetchMockStatic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-mock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// We need this import to get the extra jest assertions&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-mock-jest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Mock 'node-fetch' with 'fetch-mock-jest'. Note that using&lt;/span&gt;
&lt;span class="c1"&gt;// require here is important, because jest automatically &lt;/span&gt;
&lt;span class="c1"&gt;// hoists `jest.mock()` calls to the top of the file (before &lt;/span&gt;
&lt;span class="c1"&gt;// imports), so if we were to refer to an imported module, we &lt;/span&gt;
&lt;span class="c1"&gt;// would get a `ReferenceError`&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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;fetch-mock-jest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Cast node-fetch as fetchMock so we can access the &lt;/span&gt;
&lt;span class="c1"&gt;// `.mock*()` methods&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;FetchMockStatic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code that uses fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fetchMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should fetch a simple URL correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1234&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchMock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveFetched&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should fetch with complex assertions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/submit&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chris&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;passwordToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abcde12345&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;headers&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;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check we called the right URL, method and &lt;/span&gt;
    &lt;span class="c1"&gt;// part of the body&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchMock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/submit&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;matchPartialBody&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>fetch</category>
      <category>node</category>
      <category>typescript</category>
      <category>jest</category>
    </item>
    <item>
      <title>Using ReScript to build applications on AWS Lambda</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Thu, 26 Aug 2021 12:06:13 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/using-rescript-to-build-applications-an-aws-lambda-388g</link>
      <guid>https://dev.to/chrisarmstrong/using-rescript-to-build-applications-an-aws-lambda-388g</guid>
      <description>&lt;p&gt;&lt;em&gt;(This was originally posted at my development blog on &lt;a href="https://www.chrisarmstrong.dev"&gt;chrisarmstrong.dev&lt;/a&gt;. Check it out for more AWS and DynamoDB related articles!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rescript-lang.org"&gt;Rescript&lt;/a&gt; provides a type-safe, compile-to-JS language that delivers blazingly fast compile times with efficient and readable JavaScript output without sacrificing usability.&lt;/p&gt;

&lt;p&gt;It's most often used to build front-end applications with React but can also be used to build type-safe applications with NodeJS.  &lt;/p&gt;

&lt;p&gt;In this tutorial I'll show you how to get up and running quickly with an AWS Lambda application in ReScript. I assume you have some knowledge of ReScript and building for AWS, but in any case I'll explain what is happening as I go through.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/chris-armstrong/rescript-lambda-example"&gt;The full code accompanying this article can be found on GitHub&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;To begin, you'll need to have installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.NodeJS.org"&gt;NodeJS&lt;/a&gt;: this can be &lt;a href="https://nodejs.org/en/download/https://nodejs.org/en/download/"&gt;installed directly&lt;/a&gt; or using a tool like &lt;a href="https://github.com/nvm-sh/nvm#install--update-scripthttps://github.com/nvm-sh/nvm#install--update-script"&gt;nvm&lt;/a&gt; to manage multiple NodeJS installations. I've assumed version 14.x here which is the latest stable version&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html"&gt;AWS SAM CLI&lt;/a&gt;: Servlerless Application Model (SAM) is one of the various AWS native ways you can develop serverless
applications. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need to have an AWS account and have &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-set-up-credentials.html"&gt;configured SAM with with your AWS credentials&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In a new directory, run &lt;code&gt;npm init&lt;/code&gt; to generate a &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;rescript-lambda-example
&lt;span class="nb"&gt;cd &lt;/span&gt;rescript-lambda-example
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, install some of the modules we will need:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i rescript esbuild npm-run-all
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;ReScript is installed as an &lt;code&gt;npm&lt;/code&gt; module. In addition, we'll be using &lt;code&gt;esbuild&lt;/code&gt; to bundle our code and &lt;code&gt;npm-run-all&lt;/code&gt; to coordinate our shell scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  API Handler
&lt;/h2&gt;

&lt;p&gt;AWS Lambda API handers for NodeJS consist of a JavaScript file that exports a function of the form:&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// some handler code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the &lt;code&gt;event&lt;/code&gt; is structured based on the Lambda trigger, and the &lt;code&gt;context&lt;/code&gt; provides context information&lt;br&gt;
about the Lambda runtime environment (such as the request ID and authentication information for API Gateway&lt;br&gt;
contexts). &lt;/p&gt;

&lt;p&gt;Our Lambda will be used with API Gateway, which has &lt;a href="https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/nodejs-apig/event.json"&gt;events structured like this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"{\"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;"Chris&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isBase64Encoded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will build a simple hello-world handler that accepts a JSON-encoded body containing an object with a single &lt;code&gt;name&lt;/code&gt; field, and echo it back in the response to the HTTP client.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Code
&lt;/h3&gt;

&lt;p&gt;First, we define the structure of our incoming request body and outgoing response body and serialisation functions for them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/NameMessage.res&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * The type of the incoming body
 */&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;nameMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * The type of the response body
 */&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;nameResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Decode a JSON-encoded string using JSON.parse()
 * and assume it is the nameMessage type
 */&lt;/span&gt;
&lt;span class="nd"&gt;@scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"JSON"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@val&lt;/span&gt; 
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;parseNameMessage&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nameMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"parse"&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * Stringify the name response message
 */&lt;/span&gt;
&lt;span class="nd"&gt;@scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"JSON"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="nd"&gt;@val&lt;/span&gt; 
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;stringifyResponse&lt;/span&gt;: &lt;span class="n"&gt;nameResponse&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stringify"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These serialisers are just &lt;code&gt;JSON.parse&lt;/code&gt; (mapped as &lt;code&gt;parseNameMessage&lt;/code&gt;) and &lt;code&gt;JSON.stringify&lt;/code&gt; (as &lt;code&gt;stringifyResponse&lt;/code&gt;). These aren't particularly type safe, and as you'll see, we can easily cause runtime errors as they effectively cast whatever input the user provides into the &lt;code&gt;nameMessage&lt;/code&gt; structure.&lt;/p&gt;

&lt;p&gt;We'll see how we can build type-safe encoders/decoders in a future post, but for now, this wrapping the &lt;code&gt;JSON&lt;/code&gt; API directly us get started quickly with our example.&lt;/p&gt;

&lt;p&gt;Next, we'll define the structure of the API Gateway event and the expected response from the Lambda event handler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/ApiGateway.res&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * An API Gateway Lambda Event.
 *
 * (with a simplified interface with just what
 * we need for this example)
 */&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;awsAPIGatewayEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;,
  &lt;span class="n"&gt;isBase64Encoded&lt;/span&gt;: &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
  * An APIGateway response type. In this we
  * seralise the response body and specify the
  * status code (with any headers, if necessary)
  */&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;awsAPIGatewayResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;,
  &lt;span class="n"&gt;headers&lt;/span&gt;: &lt;span class="nn"&gt;Js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;,
  &lt;span class="n"&gt;statusCode&lt;/span&gt;: &lt;span class="kt"&gt;int&lt;/span&gt;,
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are based on the &lt;a href="https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/nodejs-apig/event.json"&gt;API Gateway Event&lt;/a&gt; structure we showed before.&lt;/p&gt;

&lt;p&gt;Next, we'll define some wrappers around NodeJS &lt;code&gt;Buffer.from()&lt;/code&gt; and &lt;code&gt;Buffer.prototype.toString()&lt;/code&gt; (which we'll need to decode base64-encoded API Gateway bodies).&lt;/p&gt;

&lt;p&gt;Lastly, we use the above two files and define our handler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/Hello.res&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Belt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Option&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;ApiGateway&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;NameMessage&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;: &lt;span class="n"&gt;awsAPIGatewayEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: &lt;span class="nn"&gt;Js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;awsAPIGatewayResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;Js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt;, &lt;span class="n"&gt;isNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="c1"&gt;// Convert body to a UTF-8 string if base64-encoded&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;isBase64Encoded&lt;/span&gt;
        ? &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;~&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;#base64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;~&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;#utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        : &lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; ? &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; : &lt;span class="nc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Parse the event body string using JSON.parse&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;, &lt;span class="n"&gt;parseNameMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract the name field&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;, &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;.&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Construct the response body&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;responseBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;message&lt;/span&gt;: &lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="n"&gt;getWithDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;, &lt;span class="s2"&gt;"there"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="s2"&gt;`,
  }

  let response = {
    statusCode: 200,
    headers: Js.Dict.fromArray([("content-type", "application/json")]),
    body: stringifyResponse(responseBody),
  }
  Js.Promise.resolve(response)
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our handler starts by decoding the body (if it happens to be base64-encoded) and checking if it is an empty string (making it &lt;code&gt;None&lt;/code&gt; if it is to short-circuit the remaining code).&lt;/p&gt;

&lt;p&gt;It then uses &lt;code&gt;parseNameMessage&lt;/code&gt; to parse the JSON-encoded body string, and then extracts the &lt;code&gt;name&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;(Note that we use &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;flatMap&lt;/code&gt; (from &lt;code&gt;Belt.Option&lt;/code&gt;) throughout, as the &lt;code&gt;event.body&lt;/code&gt; value is optional.)&lt;/p&gt;

&lt;p&gt;Lastly, we construct the response body object, and then stringify it as part of the API Gateway response (which&lt;br&gt;
also includes the &lt;code&gt;Content-Type: application/json&lt;/code&gt; header and &lt;code&gt;200&lt;/code&gt; status code).&lt;/p&gt;

&lt;p&gt;Note that if the &lt;code&gt;name&lt;/code&gt; value is not specified (i.e. it is the special value &lt;code&gt;None&lt;/code&gt;), we use &lt;code&gt;there&lt;/code&gt; as the default mapping. This is to account for when a body is not provided by the caller.&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;We need to configure two aspects of this project - the Rescript build and the serverless application deployment.&lt;/p&gt;
&lt;h3&gt;
  
  
  Building your code with Rescript
&lt;/h3&gt;

&lt;p&gt;The Rescript build is mostly boilerplate, but we'll cover it here to identify the main customisation points.&lt;/p&gt;

&lt;p&gt;The configuration I demonstrate uses a bundler, which most consider unnecessary for server-side nodejs code, but which is a good idea in AWS Lambda environments as it both drastically reduces your Lambda start up time and the time it takes to upload your code for deployment. It can also help improve the performance of your Lambda overall as there is far fewer filesystem reads from loading the required source files.&lt;/p&gt;

&lt;p&gt;The bundler I've chosen is &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt;, which is a great complement to ReScript as it is also incredibly fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;bsconfig.json&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;bsconfig.json&lt;/code&gt; in the main project directory and fill it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rescript-lambda-example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"package-specs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es6-global"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;name&lt;/em&gt; - this the name of the project, which is unused here because we a compiling an application, but in the case of libraries, it sets the namespace&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;sources&lt;/em&gt; - set the source directory (this can also be an array when there are multiple source directories)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;package-specs.module&lt;/em&gt; - set the output to ES6 modules using import/export style, instead of the default ES5/commonjs output. This ensures that the bundler (esbuild) can tree-shake out unused functions from our imports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the package.json, add the following new scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rescript-lambda-example"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-s build:rescript build:bundle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build:rescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rescript build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build:bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esbuild --outdir=build --target=node14 --platform=node lib/js/src/Hello.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"develop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-p develop:rescript develop:bundle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"develop:rescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build:rescript -- -w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"develop:bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build:bundle -- --watch"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've added two main scripts: &lt;code&gt;build&lt;/code&gt;, which does a full build and bundle (for CI/CD and deployment), and &lt;code&gt;develop&lt;/code&gt;, which uses a watch mode to continuously rebuild and rebundle. &lt;code&gt;npm-run-all&lt;/code&gt; has been used to delegate to two other &lt;code&gt;npm&lt;/code&gt; scripts and run them sequentially (&lt;code&gt;run-s&lt;/code&gt;) or in parallel (&lt;code&gt;run-p&lt;/code&gt;) as needed.&lt;/p&gt;

&lt;p&gt;Both scripts run by first using &lt;code&gt;rescript&lt;/code&gt; to compile the code (which is output to &lt;code&gt;lib/js&lt;/code&gt;) and then bundle using &lt;code&gt;esbuild&lt;/code&gt; which outputs to the &lt;code&gt;build&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;The ReScript documentation has more information on &lt;a href="https://rescript-lang.org/docs/manual/latest/build-configuration"&gt;configuring &lt;code&gt;bsconfig.json&lt;/code&gt;&lt;/a&gt; and the &lt;code&gt;rescript --help&lt;/code&gt; and &lt;code&gt;esbuild --help&lt;/code&gt; will tell you all you need to understand their respective options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up a deployment template
&lt;/h3&gt;

&lt;p&gt;SAM uses an extension of &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-reference.html"&gt;AWS CloudFormation Templates&lt;/a&gt; for its configuration.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;template.yaml&lt;/code&gt; in the root project directory with the following declarations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IndexHandler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hello.handler&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
      &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs14.x&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ApiEvent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HttpApi&lt;/span&gt;
          &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
            &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/hello&lt;/span&gt;
&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ApiGatewayURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;ServerlessHttpApi.ApiEndpoint&lt;/span&gt;
    &lt;span class="na"&gt;Export&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS::StackName}-url"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This short template:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates a Lambda function from our &lt;code&gt;build/Hello.js&lt;/code&gt; file (specifying the entry point as the &lt;code&gt;handler&lt;/code&gt; function)&lt;/li&gt;
&lt;li&gt;Adds a &lt;code&gt;HttpApi&lt;/code&gt; event trigger to the function for path &lt;code&gt;/hello&lt;/code&gt; and HTTP method &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implicitly generates an AWS API Gateway HTTP API with default deployment and stage (based on the &lt;code&gt;HttpApi&lt;/code&gt; event - &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources-function.html#sam-specification-generated-resources-function-httpapi"&gt;the logical ID of the API is &lt;code&gt;ServerlessHttpApi&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test Handler Locally
&lt;/h2&gt;

&lt;p&gt;We can test our handler locally by using the &lt;code&gt;sam local&lt;/code&gt; command (you will need to have Docker installed for this to work):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the application&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm run build
&lt;span class="c"&gt;# Start a local HTTP server&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM should spin up a HTTP server on &lt;code&gt;localhost:3000&lt;/code&gt;. We can run a quick testing using &lt;code&gt;curl&lt;/code&gt; in another terminal session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send a payload to curl (the POST HTTP method is implied)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; curl http://localhost:3000/hello &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "Chris"}'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello, Chris!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that the first time you run this may take a while, as SAM will need to download and build a Docker image to run your Lambda within.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can also see what happens when we don't provide a request body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; curl http://localhost:3000/hello &lt;span class="nt"&gt;-XPOST&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello, there!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;% 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also show how the &lt;code&gt;JSON.parse&lt;/code&gt; wrapper is just as un-type-safe as JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send a payload to curl (the POST HTTP method is implied)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; curl http://localhost:3000/hello &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"name": {}}'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello, [object Object]!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy to the environment
&lt;/h2&gt;

&lt;p&gt;The first step is packaging, which uploads your code and transforms the template to refer to its S3 location.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam package &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resolve-s3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-template-file&lt;/span&gt; template.deploy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, we can run the deployment command. This will create a new CloudFormation stack containing your Lambda and API Gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; template.deploy.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resolve-s3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; rescript-example &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_IAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should contain something like the following, which identifies the URL where your API is deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                 
---------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 ApiGatewayURL                                                                                                                       
Description         -                                                                                                                                   
Value               https://2uhonte28e.execute-api.us-east-1.amazonaws.com                                                                         
--------------------------------------------------------------------------------------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use curl again to test our deployed API to verify it is working correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; curl https://2uhonte28e.execute-api.us-east-1.amazonaws.com/hello &lt;span class="nt"&gt;-XPOST&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "World"}'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;% 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;In a future post, we'll look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve JSON handling&lt;/li&gt;
&lt;li&gt;Add multiple endpoints&lt;/li&gt;
&lt;li&gt;Make AWS API calls&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>rescript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Retry, Timeout and Cancel with fetch()</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Sun, 22 Aug 2021 11:13:34 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/retry-timeout-and-cancel-with-fetch-54ed</link>
      <guid>https://dev.to/chrisarmstrong/retry-timeout-and-cancel-with-fetch-54ed</guid>
      <description>&lt;p&gt;&lt;em&gt;(This was originally posted at my development blog on &lt;a href="https://www.chrisarmstrong.dev"&gt;chrisarmstrong.dev&lt;/a&gt;. Check it out for more AWS and DynamoDB related articles!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Although none of these are built-in, it's easy to add retries, timeouts and cancellations to your fetch() calls without needing to use a full-featured third-party library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries
&lt;/h2&gt;

&lt;p&gt;By wrapping your fetch handler in a recursive function that returns a promise, you can easily get retry behaviour:&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;fetchWithRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// split out the maxRetries option from the remaining options&lt;/span&gt;
  &lt;span class="c1"&gt;// (with a default of 3 retries)&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;maxRetries&lt;/span&gt; &lt;span class="o"&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="nx"&gt;remainingOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;remainingOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if the retryCount has not been exceeded, call again with retryCount + 1&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;retryCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&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;fetchWithRetries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retryCount&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="c1"&gt;// max retries exceeded, just throw error&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could even customise it further with additional flags to control when to retry, or to throw a &lt;code&gt;MaxRetriesError&lt;/code&gt; when the &lt;code&gt;maxRetries&lt;/code&gt; count is hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeout
&lt;/h2&gt;

&lt;p&gt;This one is fairly easy - we use &lt;code&gt;setTimeout&lt;/code&gt; to reject the promise when there is a timeout error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Create a promise that rejects after `timeout` milliseconds&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;throwOnTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
     &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
     &lt;span class="nx"&gt;timeout&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;fetchWithTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;remainingOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// if the timeout option is specified, race the fetch call&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;timeout&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;race&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;remainingOptions&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;throwOnTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;remainingOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use Promise.race to run both the &lt;code&gt;fetch()&lt;/code&gt; call and the &lt;code&gt;setTimeout()&lt;/code&gt; call at the same time - whichever resolves or rejects first will resolve or reject the &lt;code&gt;Promise&lt;/code&gt; (respectively), with the other result being ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancel
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort"&gt;This one is documented on MDN&lt;/a&gt;, but for completeness here it is below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchWithCancel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&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;AbortController&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;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&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;cancel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cancel&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, we return a tuple with the returned promise and a &lt;code&gt;cancel&lt;/code&gt; function that allows the user to cancel the request if needed.&lt;/p&gt;

&lt;p&gt;An example showing its usage is below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We don't await this call, just capture the promise&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;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fetchWithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cataas.com/cat?json=true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// await the promise to get the response&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="c1"&gt;// cancel the request (e.g. if we have rendered something else)&lt;/span&gt;
&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

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

&lt;/div&gt;



</description>
      <category>web</category>
      <category>fetch</category>
    </item>
    <item>
      <title>Building successful transactional applications on DynamoDB</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Wed, 18 Aug 2021 00:47:40 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/building-successful-transactional-applications-on-dynamodb-285l</link>
      <guid>https://dev.to/chrisarmstrong/building-successful-transactional-applications-on-dynamodb-285l</guid>
      <description>&lt;p&gt;&lt;em&gt;(This was originally posted at my development blog on &lt;a href="https://www.chrisarmstrong.dev"&gt;chrisarmstrong.dev&lt;/a&gt;. Check it out for more AWS and DynamoDB related articles!)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a successful DynamoDB solution
&lt;/h2&gt;

&lt;p&gt;Learning DynamoDB is hard work! But I hear from many developers who have dived in and tried it out because they hear about its performance and scalability and its simple API-model, but back away when it stops working because they've tried to use it like a SQL database, found it too difficult to evolve to a changing understanding of requirements, and simply not invested the time to learn how to use it well.&lt;/p&gt;

&lt;p&gt;There are now numerous courses and tutorials covering DynamoDB, which has grown off the back of increasing use of serverless architectures (where DynamoDB is a great fit) and the interesting engineering challenge that comes from using single-table design (popularised by Rick Houlihan's AWS re:Invent talks).&lt;/p&gt;

&lt;p&gt;That said, I've gathered here my own recommendations for learning about DynamoDB and strategies I've found useful for using it efficiently and practically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand its capabilities and its limits
&lt;/h3&gt;

&lt;p&gt;Spending the time to understand DynamoDB's capabilities and limits will serve you well when you start modelling a new or existing application's data structures. This will make it easier to decide how to store certain types of data to optimise its access.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey"&gt;the difference between partition and sort keys and how they work together to index an item&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html"&gt;what are secondary indexes and how they are used to enable predictable fast access to items without their primary key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;the different ways to read data, such as:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ReadingData"&gt;getting an item directly by its primary key (GetItem)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html"&gt;reading one or more items (Query) on the main index or secondary indexes&lt;/a&gt; including &lt;a href="https://www.dynamodbguide.com/querying#using-key-expressions"&gt;using key expressions&lt;/a&gt; and &lt;a href="https://www.dynamodbguide.com/filtering"&gt;filtering&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html"&gt;reading all the items in a table (Scan)&lt;/a&gt; and &lt;a href="https://www.dynamodbguide.com/scans/"&gt;why it should be an access method limited to migrations and small tables&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;knowing about the existence of &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/streamsmain.html"&gt;change streams&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transactions.html"&gt;transactions&lt;/a&gt; and what use cases they solve&lt;/li&gt;
&lt;li&gt;having some idea of operational capabilities, such as &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Backups.html"&gt;backups&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery.html"&gt;point-in-time-recovery&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/EncryptionAtRest.html"&gt;encryption&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/vpc-endpoints-dynamodb.html"&gt;VPC endpoints&lt;/a&gt;, etc. which you may never need but are good to know about when your boss inevitably asks you about them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AdditionalResources.html"&gt;AWS provides a list of excellent resources for learning DynamoDB in their documentation&lt;/a&gt;, but curiously neglects &lt;a href="https://www.dynamodbbook.com/"&gt;this excellent book on DynamoDB modelling&lt;/a&gt; which covers everything above in from beginners to professional-level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning the basics of single-table modelling
&lt;/h3&gt;

&lt;p&gt;Single-table modelling, even if you still decide to use multiple tables, is necessary to know in order to make the most of DynamoDB and its performance capabilities. It's necessary to be able to efficiently implement some one-to-many and many-to-many access patterns, and structuring your data so that your tables can store disparate data types will make it much easier to evolve your application with new data types as the need arises.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.alexdebrie.com/posts/dynamodb-single-table/"&gt;Alex Debrie gives a good overview of the what and when&lt;/a&gt; of single-table modelling, &lt;a href="https://www.youtube.com/watch?v=6yqfmXiZTlM"&gt;Rick Houlihan's&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=HaEPXoXVf2k"&gt;various AWS:ReInvent&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=jzeKPKpucS0"&gt;talks on advanced modelling&lt;/a&gt; are a good way to get a sense of the possibilities &lt;a href="https://www.jeremydaly.com/takeaways-from-dynamodb-deep-dive-advanced-design-patterns-dat403/"&gt;while scaring yourself silly then replaying them and pausing every so often&lt;/a&gt;, but the best way is probably &lt;a href="https://www.dynamodbbook.com/"&gt;Alex Debrie's book&lt;/a&gt; or one of a number of articles that are available online (such as &lt;a href="https://www.trek10.com/blog/dynamodb-single-table-relational-modeling/"&gt;this one by Forrest Brazeal&lt;/a&gt; or &lt;a href="https://www.youtube.com/watch?v=W3S1OnDqWl4"&gt;this deep-dive video series from AWS&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;You probably won't use every single-table modelling technique from the get-go - the one's I've found most useful to be across are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=""&gt;Understanding how multiple collections of data can be stored in one table&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.alexdebrie.com/posts/dynamodb-one-to-many/"&gt;Denormalisation techniques for one-to-many relationships&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Storing data and indexes separately by duplicating indexed fields into separate index properties instead of trying to share them (a management layer like &lt;a href="https://github.com/chris-armstrong/dynaglue"&gt;dynaglue&lt;/a&gt; does this automatically; see the next section)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html"&gt;Strategies for many-to-many relationships&lt;/a&gt;, including &lt;a href="https://www.sentiatechblog.com/the-spiraling-complexity-of-dynamodb-data-duplication"&gt;how to keep join-collections up to date&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use an abstraction layer for table-access and queries
&lt;/h3&gt;

&lt;p&gt;Object-relationship modelling (ORM) frameworks have a bad reputation for increasing complexity without improving the usability of SQL databases from application code, and rightly so, but at the same time they've "poisoned the well" (so to speak) for the advocacy of tools that abstract away from the database layer.&lt;/p&gt;

&lt;p&gt;Instead, what has emerged is either techniques for building directly against the application layer, or the adaption to database technology to accommodate the desires of application developers, such as no-SQL databases, the ability to store unstructured JSON data in database columns and query it with SQL, JSON-schema support in the database layer, etc.&lt;/p&gt;

&lt;p&gt;With DynamoDB, most new features have been enrichments to existing behaviour, not new ways of querying data that make it easier to query your data model (beyond things like &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/11/you-now-can-use-a-sql-compatible-query-language-to-query-insert-update-and-delete-table-data-in-amazon-dynamodb/"&gt;PartiQL&lt;/a&gt; which give a familiar SQL interface to DynamoDB but also hide non-performant queries instead of making them obvious to the developer).&lt;/p&gt;

&lt;p&gt;I've found using a lighter abstraction layer very helpful with DynamoDB, especially when dealing with single-table designs, as its made it much quicker and less error-prone to create my queries and ensuring that my indexes are populated correctly.&lt;/p&gt;

&lt;p&gt;It's also helped remove a lot of the complexity and issues coding directly against the DynamoDB API, but without hiding any performance problems (&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.select.html#ql-reference.select.parameters"&gt;which is easy to do with PartiQL&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;As a result, I developed &lt;a href="https://github.com/chris-armstrong/dynaglue"&gt;dynaglue&lt;/a&gt; for this purpose, and highly recommend the use of something like it when developing with DynamoDB (alternatives I've seen include &lt;a href="https://github.com/jeremydaly/dynamodb-toolbox"&gt;DynamoDB Toolbox&lt;/a&gt;, &lt;a href="https://github.com/typedorm/typedorm"&gt;TypeORM&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/dynamodb-onetable"&gt;DynamoDB OneTable&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Schema Validation on your collections to maintain data integrity
&lt;/h3&gt;

&lt;p&gt;You should know what you are writing and reading back from your tables, and I would argue that using a type-aware language is not enough, especially if you are just casting between a JSON structure and an internal type in TypeScript, which gives you no protection against discrepencies in data storage from different pieces of code accessing the same data, or if you need to perform transformations before you read/write data.&lt;/p&gt;

&lt;p&gt;Just like you would declare a schema for your SQL tables using DDL, maintaining a JSON-Schema and validating against it before writing to the database, helps to maintain data integrity. It makes sure that you do not accidentally introduce new properties without validating their type first or changing it's definitions inadvertently between code versions. The structure of your data becomes explicit and visible during code changes.&lt;/p&gt;

&lt;p&gt;If working in JavaScript, the &lt;a href="https://gitlab.com/honey-insurance/honeycomb-web/-/merge_requests/985"&gt;ajv&lt;/a&gt; library provides fast and compliant JSON-schema validation. You can declare your schemas in code and run them just before writing to your DynamoDB table.&lt;/p&gt;

&lt;p&gt;(On an additional note, although it would seem convenient, I would caution against re-using schema declarations you may have created for your API in your database schema validation (or vice-versa): sharing schemas for two different purposes makes it hard to evolve them separately, and introduces the risk you would change one or the other in incompatible ways. &lt;strong&gt;Always maintain separate separate schemas for your data storage and API layers - future you will be thankful!&lt;/strong&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Use appropriate key generation for your primary keys
&lt;/h3&gt;

&lt;p&gt;One of the ways you help maintain a performant DynamoDB table is by ensuring that your partition keys are as unique as possible, so that DynamoDB can distribute your documents across multiple partitions as your data grows. This is obviously hard with timeseries data (the &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-time-series.html"&gt;DynamoDB manual has specific advice for handling time-series access patterns&lt;/a&gt;), but it is relatively straightforward for typical transaction records. &lt;/p&gt;

&lt;p&gt;The ideal document identifier can be generated in a distributed fashion without hash collisions and is reasonably random that it is hard to guess and will shard the DynamoDB at high data storage levels. Even if you are not planning to build tables that run to hundreds of gigabytes, you should still ensure that your keys are random enough to avoid hash collisions.&lt;/p&gt;

&lt;p&gt;Another desirable property of identifiers is being naturally sorted based on creation order, which can be achieved with identifiers that are auto-incrementing or encode a timestamp into them.&lt;/p&gt;

&lt;p&gt;In SQL databases, an incrementing counter is often used, but this is hard with DynamoDB, especially with distributed clients, because DynamoDB does not have native functionality to produce these values, and generating them in a distributed fashion is difficult. SQL databases can do this efficiently because there is typically one server and these types of operations are done internally (as with all other query operations, such as access-planning, which is why SQL has more trouble scaling in a predictable manner, as hetrogenous and workloads are handled on the same compute layer).&lt;/p&gt;

&lt;p&gt;Using timestamps is a poor alternative as well, as even with millisecond resolution, the chance of hash collisions is quite high, especially if you are writing records at a rapid rate, and the records will be grouped close together. I've seen this pattern before, and the result is ugly - it's very easy to override existing records without realising and generating bizarre long-lived behaviour in your system caused by the hash collision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/uuid"&gt;UUIDs&lt;/a&gt; are a step up, but can be a bit cumbersome to use in their typical string representation as they are quite long being encoded in hex. They also don't have any natural sort ordering, but they are a reasonable start to generating unique identifiers in a safe way.&lt;/p&gt;

&lt;p&gt;MongoDB uses &lt;a href="https://www.mongodb.com/json-and-bson"&gt;BSON&lt;/a&gt;-IDs for its records, which give pseudo-random values which are timeseries ordered. These are great for single-table designs in serverless architectures as they can be generated in a distributed fashion, and when stored in a sort key give a natural index ordered by document creation. &lt;/p&gt;

&lt;p&gt;Unfortunately, BSON itself is a bit painful to work with in plain JavaScript, but there are other options like &lt;a href="https://www.npmjs.com/package/ksuid"&gt;KSUID&lt;/a&gt; which achieve much the same thing, but with better randomness and ease-of-use. &lt;/p&gt;

&lt;p&gt;If time-series ordering is not important to you, functions like &lt;a href="https://github.com/ai/nanoid"&gt;nanoid&lt;/a&gt; can achieve much of the same outcomes without needing to worry about hash collisions (and while being URL-friendly too).&lt;/p&gt;

&lt;h3&gt;
  
  
  Do some upfront planning and future thinking
&lt;/h3&gt;

&lt;p&gt;It can be hard with agile-development methodologies to get into the habit of planning architecturally ahead, but single-table design with DynamoDB typically assumes you know all of your access patterns upfront in order to structure your data optimally.&lt;/p&gt;

&lt;p&gt;In reality, you don't need to know everything upfront, but it is ideal to have some idea about how your data will be accessed into the short to medium term. The advantages of doing so include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;indexing fields before-hand&lt;/strong&gt; - this prevents needing to back-fill later (especially while your data set is small),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pre-allocating indexes before they are used&lt;/strong&gt; - even if you don't fill them, CloudFormation only permits deploying one new Global Secondary Index (GSI) at a time, so it can be good to look ahead and add unused indexes before you need them so you aren't coordinating multiple deploys to get them in place before you deliver code that requires them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;managing document size&lt;/strong&gt; - not only thinking about how your data is accessed, but how it will grow, can help you make earlier decisions about splitting data into multiple collections or simply storing it in your original document. While it is always easy to add new collections later, finding that your documents are growing in size as time goes on means that you still need to perform a painful data migration step when you want to refactor it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Work with the databases limitations, not against them
&lt;/h2&gt;

&lt;p&gt;DynamoDB is deliberately structured with a limited set of data management features, which makes it hard to translate relational data modelling with normalisation to its model. &lt;/p&gt;

&lt;p&gt;Learning DynamoDB means letting go of a lot of those techniques and learning new techniques to achieve the same principles you require for data integrity, performant access and ease-of-access.&lt;/p&gt;

&lt;p&gt;For example, this means using streams to propagate changes to other records or other systems, identifying when you really need full read consistency, not being afraid to duplicate data between indexes and even other documents when it makes sense for your data patterns. &lt;/p&gt;

&lt;p&gt;This even means &lt;a href="https://dev.to/posts/when-to-use-dynamodb"&gt;realising when DynamoDB is a good fit for your use cases, and when something else may be complementary&lt;/a&gt;, like ElasticSearch for text based search, or &lt;a href="https://aws.amazon.com/blogs/database/how-to-perform-advanced-analytics-and-build-visualizations-of-your-amazon-dynamodb-data-by-using-amazon-athena/"&gt;exporting to S3 and scanning your data with Athena for analytics use cases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The key part of this is learning about all its features (not just its API operations, even though you should be familiar with every part of them to use them successfully) and when to apply them.&lt;/p&gt;




&lt;p&gt;I hope some of this has been useful, and if you have any of your own recommendations from your experience modelling on DynamoDB, I'd love to hear them. You can give me a shout-out on &lt;a href="https://twitter.com/ckarmstrong"&gt;Twitter&lt;/a&gt; where I'm often posting or re-tweeting on AWS or JavaScript related things.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>When to use DynamoDB</title>
      <dc:creator>Chris Armstrong</dc:creator>
      <pubDate>Tue, 17 Aug 2021 05:50:00 +0000</pubDate>
      <link>https://dev.to/chrisarmstrong/when-to-use-dynamodb-56b8</link>
      <guid>https://dev.to/chrisarmstrong/when-to-use-dynamodb-56b8</guid>
      <description>&lt;p&gt;&lt;small&gt;&lt;em&gt;(This was originally posted at my development blog on &lt;a href="https://www.chrisarmstrong.dev"&gt;chrisarmstrong.dev&lt;/a&gt;. Check it out for more AWS and DynamoDB related articles!)&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;There's no doubt DynamoDB has surged in popularity hand-in-hand with the growth in serverless adoption, as well as &lt;a href="https://www.youtube.com/watch?v=HaEPXoXVf2k"&gt;those fascinated and horrified by Rick Houlihan's talks from AWS re:Invent explaining single-table design&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here I run through some of the key advantages and disadvantages you might want to consider when evaluating DynamoDB for your next project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use DynamoDB over a SQL database
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Operating a DynamoDB database is simplified as much as possible&lt;/strong&gt;. DynamoDB is a fully-managed service, so you are freed from the headaches of managing servers, capacity planning, scheduling and coordinating backups, multi-AZ setup and other operational concerns that come with self-managed databases. &lt;/p&gt;

&lt;p&gt;Compared to SQL, operational overhead related to schema management is also gone, because you can declare your tables in CloudFormation or Terraform, and the (almost) schemaless document-design lets you worry about evolving your document structure in code (instead of as a generally forgotten operational concern to be addressed&lt;br&gt;
at release time).&lt;/p&gt;

&lt;p&gt;DynamoDB comes with a number of automated operational concerns, not limited to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html#ddb_highavailability"&gt;automatic data replication between availability zones for high data safety and replication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;automatic capacity scaling with usage that is responsive and related to cost&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GlobalTables.html"&gt;data replication between AWS regions using global tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/BackupRestore.html"&gt;quick and simple backups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery.html"&gt;point-in-time recovery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/EncryptionAtRest.html"&gt;builtin encryption at rest, including with your keys&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A well-thought out single-table design allows your schema to evolve with minimal fuss.&lt;/strong&gt; Although data migrations may be required for some significant changes, adding fields and new collections to your table is simply just a matter of defining them in your code. Provided the relationships of your core document types are well understood from the beginning, you do not find yourself having to manually run Data-Definition Language (DDL) statements on deploy or put in a place a scheme&lt;br&gt;
to apply them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You get a consistent performance profile as your application grows.&lt;/strong&gt; DynamoDB scales from hundreds of rows to millions of rows while delivering the same performance. Even so-called "small" applications have one or two tables that grow at an exponentially faster rate than others, which eventually slows down the performance of the entire application.&lt;/p&gt;

&lt;p&gt;Provided you use DynamoDB properly, you never hit that 'we need to optimise our queries' moment, or slowdowns from large tables affecting the rest of the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL databases let developers hide performance issues from themselves&lt;/strong&gt;, which only surface as the usage of a system starts to grow. Designing for DynamoDB requires developers and system architects to understand their access patterns upfront, which means that most potential performance issues can be addressed from the beginning.&lt;/p&gt;

&lt;p&gt;With SQL, problems only manifest as tables get bigger and queries no longer perform as well as when the system was small and lightly used. By that stage, you are rushing to understand bottlenecks and fix them while the user experience suffers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding the performance profile of DynamoDB is easier for developers to understand compared with SQL.&lt;/strong&gt; Because SQL&lt;br&gt;
gives the illusion of being able to access your data flexibly and performantly when databases are small, the underlying complexity of indexes, joins and access plans (and the seemingly inscrutable decisions a database makes to implement them) can be a surprising and steep learning curve which becomes urgent at the worst times.&lt;/p&gt;

&lt;p&gt;DynamoDB on the hand has a very simple (i.e. restrictive) API and set of storage mechanisms available, and therefore understanding what does and doesn't scale in DynamoDB is much easier. Add in the predictable latency of each operation at scale with a simple set of rules, and the performance profile becomes accessible to most developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  When DynamoDB is inappropriate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DynamoDB for analytics use cases is hard.&lt;/strong&gt; It's highlighted &lt;a href="https://www.alexdebrie.com/posts/dynamodb-single-table/#the-difficulty-of-analytics"&gt;again&lt;/a&gt; and &lt;br&gt;
&lt;a href="https://rockset.com/blog/5-use-cases-for-dynamodb/"&gt;again&lt;/a&gt; that DynamoDB excels at OLTP (transactional) use cases and is &lt;br&gt;
inappropriate for OLAP (analytical and data warehousing) use cases which need to perform complex joins and run over large datasets in the one query. Without going any further into this, unless you understand your analytics use cases well and can build real-time analytics processing with DynamoDB streams, you should look elsewhere for your OLAP requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text search is inefficient&lt;/strong&gt; Another common use case is free text search or cases where you need lots of different indexes - although DynamoDB allows up to 50 GSIs, this comes with a high cost and complexity. An even more awful solution would be to scan and read all the documents on demand.&lt;/p&gt;

&lt;p&gt;As awful as it is, ElasticSearch is better start, and a hybrid-architecture can easily be achieved by using DynamoDB streams to feed updates into a paired ElasticSearch cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modelling your database upfront is difficult.&lt;/strong&gt; Although you can get away with a small amount of upfront modelling, if your application is pivoting regularly and the business domain is really uncertain, it can be hard to work with DynamoDB when your schema is constantly shifting, doubly-so if you already have something in production and need to migrate data.&lt;/p&gt;

&lt;p&gt;This is where something like SQL may present some advantages as your schema evolves, but you will still be fighting schema changes and performance issues caused by a lack of planning or consideration of performance in the long run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://acloudguru.com/blog/engineering/why-amazon-dynamodb-isnt-for-everyone-and-how-to-decide-when-it-s-for-you"&gt;Forrest Brazeal has put together a more comprehensive set of reasons why you shouldn't use DynamoDB&lt;/a&gt;, with some really important points, especially regarding developers not understanding how to use DynamoDB properly and attempting to replicate patterns from MongoDB and SQL databases and quickly running into trouble &lt;em&gt;(a topic I have plenty of thoughts about and will write about soon)&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
    </item>
  </channel>
</rss>
